为什么对象expression式中的代码可以访问kotlin中包含它的作用域的variables?

在Kotlin中,对象expression式中的代码可以从包含它的作用域访问variables,就像下面的代码一样:

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

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

高阶函数和lambda – 闭包

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

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

在Java中,您只能捕获(有效)匿名类和lambdaexpression式中的最终variables。 在Kotlin中,你可以捕捉任何variables,即使它们是可变的。

这是通过将所有捕获的variables包装在简单包装类的实例中完成的。 这些包装只有一个包含捕获的variables的字段。 由于包装的实例可以是final ,所以可以像往常一样捕获它们。

所以当你这样做的时候:

 var counter = 0 { counter++ }() // definition and immediate invocation, very JavaScript 

像这样的事情发生在引擎盖下:

 class Ref(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 implements Serializable { public T element; @Override public String toString() { return String.valueOf(element); } } 

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

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

你所说的这个概念叫做“捕捉”

有一个技巧可以在Java中应用:将可变值包装成final包装,比如AtomicReference ,编译器将停止抱怨:

 AtomicReference max = new AtomicReference<>(10); if (System.currentTimeMillis() % 2 == 0) { max.set(11) } Predicate pred = i -> i * 2 > max.get(); 

这基本上就是Kotlin中发生的事情:如果最终variables( val )被捕获,它就会被复制到lambda中。 但是,如果另一方面捕获到可变variables( var ,则其值被包装在Ref

 class Ref(var value: T) 

Refvariables是final ,因此可以毫无问题地被捕获。 因此,可变的variables可以在lambda内改变。

如果您使用javap转储类,则可以使用IntReffindkotlin, IntRef不是使用lambda的int来访问可变variables,因为在lambda范围外的javavariables实际上是最终最终的 ,这意味着您不能完全修改该variables,例如:

 // 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; 

上面的代码是平等的。 所以不要在multithreading中修改lambda范围外的可变variables。 它会导致错误的结果。

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

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