TL; DR 生成代码时,具有reified类型的函数是否应考虑类型参数的可为空性?
测试用例
考虑以下Kotlin代码;两种方法的唯一区别是类型绑定是否可以为空(Any?
)或不是(Any
)。
@Test
fun testNonNullableBound() {
val x: Int = nonNullableBound()
}
@Test
fun testNullableBound() {
val x: Int = nullableBound()
}
private inline fun <reified T : Any> nonNullableBound(): T {
return unsafeMethod()
}
private inline fun <reified T : Any?> nullableBound(): T {
return unsafeMethod()
}
其中unsafeMethod
通过在Java中定义来颠覆类型系统:
public static <T> T unsafeMethod() { return null; }
这是Kotlin 1.1.4。
预期行为
我希望这些行为等效 - 类型是有效的,因此T
的实际值已知是不可为空的,因此应该在函数内部应用null检查。 return
声明。
观察到的行为
这两起案件以不同方式失败:
testNonNullableBound
的行为符合预期(由于对unsafeMethod()
返回的值进行空检查而失败)。testNullableBound
没有按预期行事 - 在执行x
分配时失败了NPE。因此,似乎插入空检查是基于类型绑定,而不是实际类型。
分析
作为参考,相关的字节码如下。请注意testNonNullableBound
中添加的空检查。
testNonNullableBound
public final testNonNullableBound()V
@Lorg/junit/Test;()
[...]
L1
LINENUMBER 28 L1
INVOKESTATIC JavaStuff.unsafeMethod ()Ljava/lang/Object;
DUP
LDC "unsafeMethod()"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
[...]
testNullableBound
public final testNullableBound()V
@Lorg/junit/Test;()
[...]
L1
LINENUMBER 27 L1
INVOKESTATIC JavaStuff.unsafeMethod ()Ljava/lang/Object;
[...]
答案 0 :(得分:1)
因此,似乎插入空检查是基于类型绑定,而不是实际类型。
完全正确,这是应该如何工作的!
JVM函数不能具有不同的字节代码,具体取决于实际的泛型类型,因此相同的实现必须满足所有可能的结果。
内联不会影响这种情况,因为它遵循与普通函数相同的规则。它是以这种方式设计的,因此开发人员不那么惊讶。
答案 1 :(得分:1)
如果该功能是内联的,则问题不是。
来自Kotlin的documentation:
Java中的任何引用都可能为null,这使得Kotlin对严格的null安全性的要求对来自Java的对象不切实际。 Java声明的类型在Kotlin中被特别处理并称为平台类型。对这些类型放宽空检查,因此对它们的安全保证与Java相同
使用@Nullable
注释,在java方面,您可以强制执行kotlin以检查类型是否为null(使用@NotNull
)
@Nullable
public static <T> T unsafeMethod() { return null; }