【发布时间】:2012-05-16 20:23:32
【问题描述】:
final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
为什么必须将o 声明为最终结果?为什么其他具有可变变量的 JVM 语言没有这个要求?
【问题讨论】:
标签: java jvm closures language-design
final Object o;
List l = new ArrayList(){{
// closure over o, in lexical scope
this.add(o);
}};
为什么必须将o 声明为最终结果?为什么其他具有可变变量的 JVM 语言没有这个要求?
【问题讨论】:
标签: java jvm closures language-design
这不是 JVM 深度的,这一切都发生在语法糖级别。原因是通过闭包导出非最终 var 使其容易受到数据竞争问题的影响,并且由于 Java 被设计为“蓝领”语言,因此在原本温顺且安全的本地 var 的行为中发生了如此惊人的变化被认为太“先进”了。
【讨论】:
synchronized 和 volatile costructs。
不难从逻辑上推断为什么它必须是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();
}
};
但这一切都很好,因为您明确使用不同的变量,所以它的作用并不奇怪。
【讨论】: