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

时间:2017-06-14 07:48:37

标签: 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是如何做到的吗?

5 个答案:

答案 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);
    }
}

还有一些名为ByteRefShortRef等的包装器,它们包装了各种基元,因此不必将它们装箱以便被捕获。您可以在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)

enter image description here 在Android Studio 3.2中,这条好消息告诉您闭包内部的projectType var发生了什么。