当参数不可为空时,空值检查不会插入到实体类型中

TL; DR在生成代码时,具有特定类型的函数是否应该考虑类型参数的可空性?

测试用例

考虑下面的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的实际值已知是不可空的,所以在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; [...] 

所以看起来插入的空检查是基于绑定的类型,而不是实际的类型。

没错,这是应该如何工作!

JVM函数根据实际的泛型类型不能有不同的字节码,所以相同的实现必须满足所有可能的结果。

内联不会影响这种情况,因为它遵循与普通函数相同的规则。 它是这样设计的,所以开发者不会感到惊讶。

问题不在于函数是否内联。

从Kotlin的文档 :

Java中的任何引用都可能为空,这使得Kotlin对来自Java的对象的严格无效安全的要求是不切实际的。 Java声明的类型在Kotlin中被专门处理,并被称为平台类型。 这种类型的空检查是放松的,所以它们的安全保证和Java一样

在java端使用@Nullable注释,可以强制kotlin检查类型是否为null(使用@NotNull

 @Nullable public static <T> T unsafeMethod() { return null; }