为什么变量不能像在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)
所以我只能认为编译器不能确定ObjectRef
的element
字段是否被初始化,但@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至少试图帮助我们避免这种情况。