Kotlin:为什么! 运算符为jvm编译时生成空检查?

我使用https://javap.yawk.at/来检查Kotlin生成的字节码。 我发现,每当! 运算符被使用,相应的空检查被生成。 例如,对于以下kotlin代码:

fun main(args: Array<String>) { var a : Int? = null; println(a!!+2) } 

这个代码是生成的:

 public final class MainKt { public static final void main(@NotNull final String[] args) { Intrinsics.checkParameterIsNotNull((Object)args, "args"); final Integer n; final Integer a = n = null; if (n == null) { Intrinsics.throwNpe(); } System.out.println(((Number)n).intValue() + 2); } } 

我的问题是:为什么需要生成空检查。 无论如何JVM无论如何会抛出一个NPE,每当遇到一个空接收方法调用? 为什么另一个重复检查是必要的

!!的语义!! 表达式是它执行一个空的检查在特定的位置!! 运算符,如果被检查的表达式为null,则抛出特定类型的异常( KotlinNullPointerException ,标准NullPointerException的子类)。 抛出一个特定的异常子类清楚地表明异常是由于一个空检查失败所致!! 操作员,因此可以更容易地跟踪生产中发生的原因。

即使Java在随后的表达式中(也可能不这样做,根据您如何使用您应用的值)来抛出NPE,它将是一个通用的NPE,而不是Kotlin特有的NPE。 此外,堆栈跟踪中的行号可能与其上的行号不同!! 被使用,使得难以理解的问题。

问题是JVM不能及早地抛出NPE。 您不总是立即使用非空类型。 使用null断言可以防止null通过你的代码传播太远以至于无法跟踪。 假设你有这个班

 class Foo { var bar : Bar = null fun foo() {doSomethingWitNonNullBar(bar)} } 

这里的吧在Foo设置后不会立即使用。 如果你不小心写了类似foo.bar = null东西,稍后在不同的线程上调用foo.foo() ,真的很难知道这个null是从哪里来的。 这可能看起来很愚蠢,但是当你的代码是大量的可空类型和非空类型的混合时,它会很快成为真正的交易。 没有断言,如果类型不为null,则不能确定。

我只看到一个原因:字符串操作。

 val s = null; val s2 = "abc" + s; // abcnull val s3 = "abc" + s!!; // NPE