为什么对象表达式中的代码可以访问kotlin中包含的变量?

在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中,不允许这样做,因为对象的生命周期与本地变量不同,所以当您尝试访问对象时, enterCountclickCount可能无效。 有人能告诉我Kotlin是如何做到这一点的吗?

在Kotlin中,与Java不同,lambda表达式或匿名函数(以​​及本地函数和对象表达式)可以访问和修改它们的闭包 – 在外部范围中声明的变量。 这种行为是按照设计的。

高阶函数和lambda – 闭包

为什么Java不允许这样做,而Kotlin呢 – 捕获闭包引入了额外的运行时间开销。 Java使用简单而快速的方法来降低功能。 另一方面,Kotlin为您提供了更多功能 – 功能,但是它也在后台生成更多的代码来支持它。

最后是编写更少的代码来实现某些东西。 如果你想把上面的代码翻译成Java,那将更加复杂。

在Java中,您只能捕获(有效)匿名类和lambda表达式中的最终变量。 在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); } } 

还有一些额外的叫做ByteRefShortRef等的包装器,它们包装了各种基本ShortRef ,所以不必为了被捕获而装箱。 你可以在这个文件中找到所有的包装类。

学分转到Kotlin in Action书中,其中包含了这些信息的基础知识,以及这里使用的例子。

如果您使用javap转储类,则可以使用IntRef找到kotlin, IntRef不是使用lambda的int来访问可变变量,因为在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范围外的可变变量。 它会导致错误的结果。

另一个很好的用法是你不需要修改lambda范围外的可变变量,并想改进性能优化。 您可以使用额外的不可变变量来实现lambda的性能,例如:

 var mutable = 1; val immutable = mutable; // kotlin compiler will using int val inc = { immutable + 1 };