8

我有以下代码片段:

val foo: String? = null
foo.run { println("foo") }

我这里有一个可以为空的变量foo,它实际上设置为null后跟一个非安全.run()调用。

当我运行代码片段时,foo尽管该run方法是在null. 这是为什么?为什么没有NullPointerException?为什么编译器允许对可选值进行非安全调用?

如果我通过println(foo)了,我会在控制台中得到一个很好的多汁null,所以我认为可以安全地假设它foo实际上是null.

4

3 回答 3

11

我相信,有两件事可能会让人感到意外:允许这种调用的语言语义,以及当这段代码执行时在运行时会发生什么。

从语言方面来看,Kotlin 允许可以为空的接收器,但仅限于扩展。要编写一个接受可空接收器的扩展函数,应该显式编写可空类型,或者为类型参数使用可空上限(实际上,当您不指定上限时,默认值是可空的Any?):

fun List<*>?.isEmptyOrNull() = this == null || size == 0 // explicit nullable type

fun <T : CharSequence?> T.nullWhenEmpty() = if ("$this" == "") null else this // nullable T

fun <T> T.identity() = this // default upper bound Any? is nullable

此功能用于kotlin-stdlib多个地方:请参阅CharSequence?.isNullOrEmpty(), CharSequence?.isNullOrBlank(),?.orEmpty()用于容器String?.orEmpty(), 甚至Any?.toString(). 您询问的某些函数(例如T.let,T.run您询问的某些函数)只是不提供类型参数的上限,并且默认为 nullable Any?。并T.use提供一个可为空的上限Closeable?

在幕后,即从运行时的角度来看,扩展调用不会编译到 JVM 成员调用指令INVOKEVIRTUAL中,INVOKEINTERFACE或者INVOKESPECIAL(JVM 检查此类调用的第一个参数,即隐式this,是否为空,如果是则抛出 NPE ,这就是 Java 和 Kotlin 成员函数的调用方式)。相反,Kotlin 扩展函数被编译成静态方法,接收者只是作为第一个参数传递。INVOKESTATIC使用不检查参数是否为空的指令调用此类方法。

请注意,当扩展的接收者可以为空时,Kotlin 不允许您在需要非空值而不首先检查它是否为空的情况下使用它:

fun Int?.foo() = this + 1 // error, + is not defined for nullable Int?
于 2017-08-02T23:34:30.880 回答
4

为了补充@holi-java 所说的内容,您的代码根本没有任何不安全之处。无论是否为空println("foo")都是完全有效的。foo如果你尝试过类似的东西

foo.run { subString(1) }

这将是不安全的,您会发现如果没有某种空值检查它甚至无法编译:

foo.run { this?.subString(1) }
// or
foo?.run { subString(1) }
于 2017-08-02T21:03:57.363 回答
2

这是因为顶级函数run接受任何Any& Any?。因此Kotlin 在运行时不会检查具有Null Receiver的扩展函数。

//                 v--- accept anything
public inline fun <T, R> T.run(block: T.() -> R): R = block()

事实上,内联函数run是由 Kotlin 生成的,如果receiver可以为nullable则没有任何断言,因此它更像是为 Java 代码生成的 noinline 函数,如下所示:

public static Object run(Object receiver, Function1<Object, Object> block){
   //v--- the parameters checking is taken away if the reciever can be nullable
  //Intrinsics.checkParameterIsNotNull(receiver, "receiver");

  Intrinsics.checkParameterIsNotNull(block, "block");
  // ^--- checking the `block` parameter since it can't be null 
}

如果你想以安全的方式调用它,你可以使用safe-call 操作符?.,例如:

val foo: String? = null

// v--- short-circuited if the foo is null
foo?.run { println("foo") }
于 2017-08-02T20:52:50.243 回答