在Kotlin中,对象表达式中的代码可以从包含它的作用域中访问变量,就像下面的代码一样:
fun countClicks(window: JComponent) {
var clickCount = 0
var enterCount = 0
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
override fun mouseEntered(e: MouseEvent) {
enterCount++
}
})
}
但为什么呢?在Java中,不允许这样做,因为对象的生命周期与局部变量不同,因此当您尝试访问时,enterCount
或clickCount
可能无效。宾语。有人能告诉我Kotlin是如何做到的吗?
答案 0 :(得分:17)
在Java中,您只能捕获(有效)匿名类和lambdas中的最终变量。在Kotlin中,您可以捕获任何变量,即使它们是可变的。
这是通过将任何捕获的变量包装在简单包装类的实例中来完成的。这些包装器只有一个包含捕获变量的字段。由于包装器的实例可以是final
,因此可以像往常一样捕获它们。
所以当你这样做时:
var counter = 0
{ counter++ }() // definition and immediate invocation, very JavaScript
这样的事情发生在引擎盖下:
class Ref<T>(var value: T) // generic wrapper class somewhere in the runtime
val counter = Ref(0); // wraps an Int of value 0
{ counter.value++ }() // captures counter and increments its stored value
包装类的实际实现是用Java编写的,如下所示:
public static final class ObjectRef<T> implements Serializable {
public T element;
@Override
public String toString() {
return String.valueOf(element);
}
}
还有一些名为ByteRef
,ShortRef
等的包装器,它们包装了各种基元,因此不必将它们装箱以便被捕获。您可以在this file中找到所有包装类。
致谢会转到Kotlin in Action一书,其中包含此信息的基础知识以及此处使用的示例。
答案 1 :(得分:5)
在Kotlin中,与Java不同,lambda表达式或匿名函数(以及本地函数和对象表达式)可以访问和修改它们的闭包 - 在外部作用域中声明的变量。此行为是按设计的。
Higher order functions and lambdas - Closures
为什么Java不允许这样做,而Kotlin也是如此 - 捕获闭包会带来额外的运行时开销。 Java以功能为代价使用简单快速的方法。另一方面,Kotlin为您提供了更多功能 - 功能,但它也会在幕后生成更多代码来支持它。
最后,它是关于编写更少的代码来实现某些目标。如果要将上述代码转换为Java,则会更复杂。
答案 2 :(得分:3)
您所指的概念称为“捕获”。
可以在Java中应用一个技巧:将可变值包装到final
包装器中,例如AtomicReference<T>
,编译器将停止抱怨:
AtomicReference<Integer> max = new AtomicReference<>(10);
if (System.currentTimeMillis() % 2 == 0) {
max.set(11)
}
Predicate<Integer> pred = i -> i * 2 > max.get();
这基本上也是在Kotlin中发生的事情:如果正在捕获最终变量(val
),它只会被复制到lambda中。但另一方面,如果正在捕获可变变量(var
),则其值将包含在Ref
的实例中:
class Ref<T>(var value: T)
Ref
变量为final
,因此可以毫无问题地捕获。因此,可变变量可以在lambda中更改。
答案 3 :(得分:2)
IF 您使用javap
转储类,您可以使用IntRef
而非int
找到kotlin,以便lambda访问其范围之外的可变变量,因为在lambda范围之外的java变量是有效最终或最终,这意味着您根本无法修改变量,例如:
// Kotlin
var value = 1; // kotlin compiler will using IntRef for mutable variable
val inc = { value++ };
inc();
println(value);// 2;
//Java
IntRef value = new IntRef();
value.element = 1;
Runnable inc=()-> value.element++;
inc.run();
println(value.element);// 2;
上面的代码是相等的。所以不要在多线程中修改lambda范围内的 mutable 变量。这会导致错误的结果。
另一个好的用法是你不需要修改lambda范围内的 mutable 变量,并希望改进性能优化。您可以使用其他不可变变量来实现lambda的性能,例如:
var mutable = 1;
val immutable = mutable; // kotlin compiler will using int
val inc = { immutable + 1 };
答案 4 :(得分:0)