final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
为什么必须o
宣布最终?为什么其他具有可变变量的 JVM 语言没有这个要求?
final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
为什么必须o
宣布最终?为什么其他具有可变变量的 JVM 语言没有这个要求?
这不是 JVM 深度的,这一切都发生在语法糖级别。原因是通过闭包导出非最终 var 使其容易受到数据竞争问题的影响,并且由于 Java 被设计为“蓝领”语言,因此在原本温顺且安全的本地 var 的行为中发生了如此惊人的变化被认为太“先进”了。
不难从逻辑上推断为什么它必须是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();
}
};
但这一切都很好,因为你明确地使用了不同的变量,所以它的作用也就不足为奇了。