在java中,为什么需要将闭包变量声明为final?

时间:2012-05-16 20:23:33

标签: java jvm closures language-design

final Object o;

List l = new ArrayList(){{
    // closure over o, in lexical scope
    this.add(o);
}};

为什么o必须被宣布为最终?为什么其他具有可变变量的JVM语言没有这个要求呢?

2 个答案:

答案 0 :(得分:3)

这不是JVM-deep,它都发生在句法 - 糖级别。原因是通过闭包导出非final var会使其容易受到datarace问题的影响,并且由于Java被设计为“蓝领”语言,因此在其他温和且安全的局部变量的行为中出现了令人惊讶的变化被认为太“先进”了。

答案 1 :(得分:1)

从逻辑上推断为什么它必须是final并不难。

在Java中,当局部变量被捕获到匿名类中时,它将按值复制。这样做的原因是对象可能比当前函数调用寿命更长(例如,它可能被返回等),但局部变量仅与当前函数调用一样长。因此,不可能简单地“引用”变量,因为那时它可能不存在。 Python,Ruby,JavaScript等一些语言允许您在范围消失后引用变量,方法是保留对堆中环境的引用。但这对JVM来说很难做到,因为局部变量是在函数的堆栈帧上分配的,当函数调用完成时会被破坏。

现在,由于它被复制,因此有两个变量副本(如果有更多的闭包捕获此变量,则更多)。如果它们是可分配的,那么您可以更改其中一个而不更改另一个。例如,假设:

Object o;

Object x = new Object(){
    public String toString() {
        return o.toString();
    }
};
o = somethingElse;
System.out.println(x.toString()); // prints the original object, not the re-assigned one
                                  // even though "o" now refers to the re-assigned one

由于范围中只有一个o变量,您可能希望它们引用相同的内容。在上面的示例中,在您分配给o之后,您希望稍后从对象访问o以引用新值;但事实并非如此。这对程序员来说是令人惊讶和意外的,并且违反了使用相同变量引用相同内容的原则。

所以为了避免这种意外,他们要求你不能在任何地方分配它;即它必须是final

现在,您当然可以从非final变量初始化final变量。在闭包内,您仍然可以将final变量分配给其他非final

Object a; // non-final
final Object o = a;

Object x = new Object(){
    Object m = o; // non-final
    public String toString() {
        return ,.toString();
    }
};

但是这一切都很好,因为你明确地使用了不同的变量,所以它的作用并不奇怪。