Почему переменная не может быть правильно инициализирована в встроенной функции, как в java?

Мы знаем, что лямбда-тело лениво хорошо, потому что, если мы не будем называть лямбда, код в лямбда-теле никогда не будет называться.

Мы также знаем на любом языке функций, что переменная может использоваться в функции / lambda, даже если она не инициализирована, например, javascript, ruby, groovy и .etc, например, код groovy ниже может работать нормально:

def foo def lambda = { foo } foo = "bar" println(lambda()) // ^--- return "bar" 

Мы также знаем, что мы можем получить доступ к неинициализированной переменной, если блок catch инициализировал переменную, когда исключение возникает в try-блоке в Java, например:

 // v--- m is not initialized yet int m; try{ throw new RuntimeException(); } catch(Exception ex){ m = 2;} System.out.println(m);// println 2 

Если лямбда лениво, почему Котлин не может использовать неинициализированную переменную в лямбда? Я знаю, что Kotlin – это язык с нулевой безопасностью, поэтому компилятор будет анализировать код сверху донизу, включая тело лямбда, чтобы убедиться, что переменная инициализирована. поэтому лямбда-тело не «лениво» во время компиляции. например:

 var a:Int val lambda = { a }// lambda is never be invoked // ^--- a compile error thrown: variable is not initialized yet a = 2 

Q : Но почему код ниже также не может работать? Я не понимаю этого, поскольку переменная является фактически окончательной в Java, если вы хотите изменить значение переменной, вы должны использовать ObjectRef вместо этого, и этот тест противоречит моим предыдущим выводам: «Тело лямбда не лениво во время компиляции " .например:

 var a:Int run{ a = 2 }// a is initialized & inlined to callsite function // v--- a compile error thrown: variable is not initialized yet println(a) 

Поэтому я могу только думать, что компилятор не может уверен, что поле element в ObjectRef является инициализированным или нет, но @hotkey отрицает мои мысли. Почему ?

В : почему встроенные функции Kotlin не могут нормально работать, даже если я инициализирую переменную в catch-блоке, например, как в java? например:

 var a: Int try { run { a = 2 } } catch(ex: Throwable) { a = 3 } // v--- Error: `a` is not initialized println(a) 

Но @hotkey уже упомянул, что вы должны использовать выражение try-catch в Kotlin для инициализации переменной в его ответе , например:

 var a: Int = try { run { 2 } } catch(ex: Throwable) { 3 } // v--- println 2 println(a); 

В : Если это действительно так, почему я не называю run напрямую? например:

 val a = run{2}; println(a);//println 2 

Однако приведенный выше код может отлично работать в java, например:

 int a; try { a = 2; } catch (Throwable ex) { a = 3; } System.out.println(a); // println 2 

    Q: Но почему код ниже также не может работать?

    Потому что код может измениться. В точке, где определена лямбда, переменная не инициализируется, поэтому, если код изменен, а лямбда вызывается непосредственно после этого, это будет недействительным. Компилятор kotlin хочет удостовериться, что нет абсолютно никакой возможности доступа к неинициализированной переменной до ее инициализации даже прокси.

    В: почему встроенные функции Kotlin не могут нормально работать, даже если я инициализирую переменную в catch-блоке, например, как в java?

    Поскольку run не является специальным, и компилятор не может знать, когда тело выполняется. Если вы считаете, что run не выполняется, компилятор не может гарантировать, что переменная будет инициализирована.

    В измененном примере он использует выражение try-catch, чтобы по существу выполнить a = run { 2 } , который отличается от run { a = 2 } потому что результат гарантируется возвращаемым типом.

    В: Если это действительно так, почему я не вызываю запуск напрямую?

    Это то, что происходит. Что касается окончательного кода Java, то факт заключается в том, что Java не соответствует точно таким же правилам Kotlin, и это происходит в обратном порядке. Просто потому, что что-то возможно в Java, не означает, что он будет действительным Котлином.

    Вы можете сделать переменную ленивой со следующим …

     val a: Int by lazy { 3 } 

    Очевидно, вы могли бы использовать функцию вместо 3. Но это позволяет компилятору продолжить работу и гарантирует, что a будет инициализирован перед использованием.

    редактировать

    Хотя вопрос, похоже, «почему это невозможно». Я в том же разуме, что я не понимаю, почему нет (в пределах разумного). Я думаю, что у компилятора достаточно информации, чтобы понять, что объявление лямбда не является ссылкой на любую из переменных замыкания. Поэтому я думаю, что при использовании лямбда и переменных, которые он ссылается, не было инициализировано.

    Тем не менее, вот что я сделал бы, если бы авторы компилятора не согласились с моей оценкой (или слишком долго, чтобы обойти эту функцию).

    В следующем примере показан способ сделать ленивую локальную переменную инициализации (для версии 1.1 и более поздней)

     import kotlin.reflect.* //... var a:Int by object { private var backing : Int? = null operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = backing ?: throw Exception("variable has not been initialized") operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { backing = value } } var lambda = { a } // ... a = 3 println("a = ${lambda()}") 

    Я использовал анонимный объект, чтобы показать мужество того, что происходит (и потому, что lazy вызвал ошибку компилятора). Объект может быть превращен в функцию как lazy .

    Теперь мы потенциально возвращаемся к исключению во время выполнения, если программист забывает инициализировать переменную до ее ссылки. Но Котлин попытался хотя бы помочь нам избежать этого.

    Давайте будем гением компьютера.