3
final Object o;

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

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

4

2 回答 2

3

这不是 JVM 深度的,这一切都发生在语法糖级别。原因是通过闭包导出非最终 var 使其容易受到数据竞争问题的影响,并且由于 Java 被设计为“蓝领”语言,因此在原本温顺且安全的本地 var 的行为中发生了如此惊人的变化被认为太“先进”了。

于 2012-05-16T20:25:50.123 回答
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();
    }
};

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

于 2012-06-28T20:35:33.807 回答