我们知道lambda主体是惰性的,因为如果我们不调用lambda,就永远不会调用lambda主体中的代码。
我们还知道,在任何函数语言中,即使没有初始化变量,也可以在函数/lambda中使用变量,例如javascript、ruby、groovy和.etc,例如,下面的groovy代码可以很好地工作:
def foo
def lambda = { foo }
foo = "bar"
println(lambda())
// ^--- return "bar"我们还知道,如果catch块已初始化了变量,则可以访问未初始化的变量,例如,在Java的try-block中引发异常时:
// 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 = 2Q:但是为什么下面的代码也不能工作呢?我不明白,因为变量是Java中的effectively-final,如果您想要更改变量值,就必须使用ObjectRef,这个测试与我以前的结论相矛盾:“lambda在编译时不是懒惰的”.for示例:
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中的ObjectRef字段是否已初始化,但@热键否认了我的想法。为什么
Q:为什么Kotlin内联函数不能很好地工作,即使我在catch块中初始化变量,就像在java中一样?例如:
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);Q:如果实际情况是这样,为什么我不直接调用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发布于 2017-07-21 18:04:38
问:但是为什么下面的代码也不能工作呢?
因为代码可以改变。在定义lambda时,变量不会被初始化,因此如果代码被更改,然后直接调用lambda,则变量将无效。kotlin编译器希望确保在未初始化的变量被初始化之前,绝对不可能访问它,即使是通过代理。
问:为什么Kotlin内联函数不能很好地工作,即使我像java一样在catch块中初始化变量?
因为run不是特殊的,编译器无法知道何时执行主体。如果考虑不执行run的可能性,则编译器无法保证变量将被初始化。
在已更改的示例中,它使用try-catch表达式实质上执行a = run { 2 },这与run { a = 2 }不同,因为返回类型保证了结果。
问:如果实际情况是这样,为什么我不直接调用运行?
本质上这就是发生的事情。关于最后的Java代码,事实是Java并不遵循Kotlin完全相同的规则,相反的情况也是如此。仅仅因为Java中的某些东西是可能的,并不意味着它将是有效的Kotlin。
发布于 2017-07-21 20:11:00
您可以使用以下方法使变量懒惰..。
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一样转换为函数。
现在,如果程序员忘记在引用变量之前初始化它,那么我们可能会回到运行时异常。但科特林至少试着帮我们避免这种情况。
https://stackoverflow.com/questions/45243255
复制相似问题