为什么变量不能像在java中那样在内联函数中正确初始化?

我们知道lambda体是懒惰的 ,因为如果我们不调用lambda,lambda体中的代码永远不会被调用。

我们也知道在任何函数语言中,一个变量可以在函数/ lambda中使用,即使它没有被初始化,比如javascript,ruby,groovy和.etc,例如下面的groovy代码可以正常工作:

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

我们也知道,如果在Java中的try-block中引发异常时catch块已经初始化变量,我们可以访问一个未初始化的变量,例如:

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

如果lambda是懒惰的,为什么Kotlin不能在lambda中使用未初始化的变量? 我知道Kotlin是一个空安全的语言,所以编译器会从上到下分析代码,包括lambda体,以确保变量被初始化。 所以lambda体在编译时不是“懒惰”的。 例如:

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

:但为什么下面的代码也不能工作? 我不明白,因为这个变量在Java中是有效的 ,所以如果你想改变变量的值,你必须使用ObjectRef ,而这个测试与我以前的结论相矛盾:“lambda body在编译时不是懒惰的“ 。例如:

 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) 

所以我只能认为编译器不能确定ObjectRefelement字段是否被初始化,但@hotkey否认了我的想法。 为什么

:为什么Kotlin内联函数不能正常工作,即使我像在java中一样在catch-block中初始化变量? 例如:

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

但是,@hotkey已经提到你应该在Kotlin中使用try-catch表达式来初始化他的答案中的一个变量,例如:

 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 

问:但为什么下面的代码也不能工作?

因为代码可以改变。 在定义了lambda的地方,变量没有被初始化,所以如果代码改变了,之后直接调用了lambda,它将是无效的。 kotlin编译器希望确保在初始化之前绝对没有办法访问未初始化的变量,即使通过代理也是如此。

问:为什么Kotlin内联函数不能正常工作,即使我像java中一样在catch-block中初始化变量?

因为run不是特别的,编译器不能知道什么时候执行正文。 如果考虑到run没有被执行的可能性,那么编译器不能保证该变量将被初始化。

在更改的示例中,它使用try-catch表达式实质上执行a = run { 2 } ,这与run { a = 2 }不同,因为结果由返回类型保证。

问:如果实际情况是这样,为什么我不直接调用运行?

这基本上是发生了什么。 关于最终的Java代码,事实是Java并不遵循Kotlin完全相同的规则,反过来也是如此。 仅仅因为在Java中有可能并不意味着Kotlin就是有效的。

你可以使变量懒惰与以下…

 val a: Int by lazy { 3 } 

显然,你可以使用一个函数来代替3.但是这允许编译器继续并保证a在使用之前被初始化。

编辑

虽然这个问题似乎是“为什么不能这样做”。 我在同一个思维框架,我不明白为什么不(原因)。 我认为编译器有足够的信息来弄清楚lambda声明不是对任何闭包变量的引用。 所以,我认为它可能会显示一个不同的错误,当使用lambda时,它所引用的变量还没有被初始化。

也就是说,如果编译器编写者不同意我的评估(或花费太长的时间来避免这个特性),我会这样做。

下面的例子展示了一种懒惰的局部变量初始化(对于版本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功能。

现在,如果程序员在被引用之前忘记初始化变量,我们可能会回到运行时异常。 但是Kotlin至少试图帮助我们避免这种情况。