在java中,为什么closured变量需要声明为final?

在java中,为什么closured变量需要声明为final?,java,jvm,closures,language-design,Java,Jvm,Closures,Language Design,为什么必须宣布o为最终版本?为什么其他具有可变变量的JVM语言没有这个要求?这不是JVM的深度,所有这些都发生在语法层面。原因是通过闭包导出非最终var容易受到datarace问题的影响,而且,由于Java被设计为“蓝领”语言,因此在一个原本温和而安全的本地var的行为上发生如此惊人的变化被认为过于“先进”.从逻辑上不难推断为什么它必须是最终的 Object o; Object x = new Object(){ public String toString() { r

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

这不是JVM的深度,所有这些都发生在语法层面。原因是通过闭包导出非最终var容易受到datarace问题的影响,而且,由于Java被设计为“蓝领”语言,因此在一个原本温和而安全的本地var的行为上发生如此惊人的变化被认为过于“先进”.

从逻辑上不难推断为什么它必须是
最终的

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

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

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

final Object o;

List l = new ArrayList(){{
    // closure over o, in lexical scope
    this.add(o);
}};
由于作用域中只有一个
o
变量,因此您希望它们引用相同的内容。在上面的示例中,在您分配给
o
之后,您将期望稍后从对象访问
o
,以引用新值;但事实并非如此。这对程序员来说是令人惊讶和意外的,并且违反了使用相同变量引用相同事物的原则

所以为了避免这种意外,他们要求你不能把它分配到任何地方;i、 e.必须是
最终版本

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
当然,现在仍然可以从非
final
变量初始化
final
变量。在闭包内部,您仍然可以将
final
变量分配给其他非
final
变量

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

但这一切都是好的,因为您显式地使用了不同的变量,所以它的作用也就不足为奇了。

这是一个非常奇怪的设计决策,假定类实例的final ref的字段可以随时更改。@DustinGetz当多个线程争用同一个var时,就会发生这种情况——至少有一个线程在读,一个线程在写它。@Vlad final类实际上与此无关。这些字段通常被认为是数据竞赛的对象。@Marko:这正是我的观点。这个问题只能在一个层面上解决,给人一种虚假的安全感。(我已经更正了“final class”->“final ref”。@Vlad但是共享字段是线程之间任何可以想象的数据共享场景的基础!如果没有这个问题,该语言就不需要
synchronized
volatile
构造。”但这在JVM中很难做到,因为局部变量分配在函数的堆栈帧上,在函数调用完成时,堆栈帧会被破坏。“等等,什么?如果是这样的话,java可能会有析构函数。@DustinGetz:我说的是变量。你把它和物体混淆了。在Java中,对象永远不能是值。值是原语和引用,与析构函数没有任何关系。