为什么 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?
我有以下代码片段:
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?