Котлин: Почему? оператор генерирует нулевые проверки при компиляции для 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 всякий раз, когда он сталкивается с вызовом метода на пустом приемнике? Почему нужна еще одна избыточная проверка?

Семантика !! выражение заключается в том, что он выполняет нулевую проверку в определенном месте, где есть !! используется оператор и выбрасывает определенный вид исключения ( KotlinNullPointerException , подкласс стандартного исключения NullPointerException ), если проверяемое выражение равно null. Выделение конкретного подкласса исключений дает понять, что исключение вызвано сбоем нулевой проверки в !! оператора, и поэтому облегчает отслеживание причины, когда это происходит в процессе производства.

Даже если Java сделала бросок NPE в последующем выражении (которое оно может или не может делать, в зависимости от того, как вы используете значение, к которому вы применили !! ), это будет общий NPE, а не специфический для Kotlin. Кроме того, номер строки в stacktrace может отличаться от номера строки, на которой !! , что затрудняет понимание проблемы.

Проблема в том, что JVM не может набросать NPE достаточно рано. Вы не всегда используете тип nonnull немедленно. Использование нулевых утверждений может препятствовать тому, чтобы null не распространялся через ваш код слишком далеко, чтобы отслеживать. Предположим, у вас есть этот класс

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

Здесь панель сразу не используется после того, как она установлена ​​в Foo . Если вы случайно написали что-то вроде foo.bar = null и foo.foo() позже в другом потоке, будет очень сложно узнать, откуда этот нуль. Это может показаться глупым, но это быстро станет реальной сделкой, когда ваш код представляет собой сочетание множества типов с нулевыми значениями и типов nonnull. Без утверждений вы не можете быть уверены, что тип не равен NULL.

Я вижу только одну причину: строковые операции.

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