我们知道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)
所以我只能想到编译器无法确定element
中的ObjectRef
字段是否已初始化,但@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
表达式初始化his answer中的变量,例如:
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
答案 0 :(得分:2)
问:但为什么下面的代码也无法正常工作?
因为代码可以改变。在定义lambda的位置,变量未初始化,因此如果代码被更改并且之后直接调用lambda将无效。 kotlin编译器希望确保在初始化之前无法访问未初始化的变量,即使是代理也是如此。
问:为什么Kotlin内联函数无法正常运行,即使我在catch-block中初始化变量就像在java中一样?
因为run
并不特殊,编译器无法知道主体何时执行。如果考虑run
未执行的可能性,则编译器无法保证变量将被初始化。
在更改的示例中,它使用try-catch表达式实质上执行a = run { 2 }
,这与run { a = 2 }
不同,因为返回类型保证了结果。
问:如果真的是那个,为什么我不直接打电话?
基本上就是这样。关于最终的Java代码,事实是Java不遵循Kotlin完全相同的规则,反过来也是如此。仅仅因为Java中的某些东西是可能的并不意味着它将是有效的Kotlin。
答案 1 :(得分:1)
你可以使用以下内容使变量变得懒惰......
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确实试图帮助我们避免这种情况。