为什么 unsafe .运行() 调用在 Kotlin 中的空值上工作正常?

Why do unsafe .run() call works fine on a null value in Kotlin?

我有以下代码片段:

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

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

当我 运行 代码片段时,尽管在 null 上调用了 run 方法,但我得到了 foo 打印出来。这是为什么?为什么没有 NullPointerException?为什么编译器允许对可选值进行非安全调用?

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

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

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

Indeed,如果 receiver 可以 nullable[=31,则内联函数 run 由 Kotlin 生成,没有任何断言=],所以它更像是生成到 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") }

补充一下@holi-java 所说的,您的代码根本没有任何不安全之处。 println("foo") 无论 foo 是否为 null 都完全有效。如果您尝试过

foo.run { subString(1) }

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

foo.run { this?.subString(1) }
// or
foo?.run { subString(1) }

我相信,有两件事可能都令人感到意外:允许此类调用的语言语义,以及执行此代码时在运行时发生的情况。

从语言方面来说,Kotlin 允许 nullable receiver, but only for extensions。要编写一个接受可为空接收器的扩展函数,要么显式编写可为空类型,要么为类型参数使用可为空上限(实际上,当您不指定上限时,默认为可为空 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() for containers and String?.orEmpty(), and even Any?.toString(). Some functions like T.let, T.run that you asked about and some others just don't provide an upper bound for the type parameter, and that defaults to nullable Any?. And T.use 提供了一个可为空的上限 Closeable?

在底层,即从运行时的角度来看,扩展调用编译成JVM成员调用指令INVOKEVIRTUAL, INVOKEINTERFACE or INVOKESPECIAL (the JVM checks the first argument of such calls, the implicit this, for being null and throws an NPE if it is, and this is how Java & Kotlin member functions are called). Instead, the Kotlin extension functions are compiled down to static methods, and the receiver is just passed as the first argument. Such a method is called with the INVOKESTATIC不检查为 null 的参数。

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

fun Int?.foo() = this + 1 // error, + is not defined for nullable Int?