当参数不可为空时,不会为具体化类型插入空检查

Null checks not inserted for reified type when param is non-nullable

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 的实际值是不可为 null 的,因此应该在函数内部应用 null 检查之前return 声明。

观察到的行为

两种情况以不同的方式失败:

因此空检查的插入似乎是基于类型绑定,而不是实际类型。

分析

供参考,相关字节码如下。请注意 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;
   [...]

So it appears the insertion of null checks is based on the type bound, rather than the actual type.

完全正确,这就是它应该工作的方式!

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

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

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

来自 Kotlin 的 documentation

Any reference in Java may be null, which makes Kotlin's requirements of strict null-safety impractical for objects coming from Java. Types of Java declarations are treated specially in Kotlin and called platform types. Null-checks are relaxed for such types, so that safety guarantees for them are the same as in Java

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

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