所以,这就是我所理解的:Java不支持闭包,因此它将变量从包含作用域复制到嵌套作用域中,以便稍后可用。因为这是一个副本,所以无法同步原始副本和副本,并且变量被强制为最终变量,因此开发人员无法更改它并期望它更新。这种理解部分来自这些answers
这使我们能够使用这段代码:
public class SimpleClosure {
public static void main(String[] args) {
new SimpleClosure().doStuff();
}
public void doStuff() {
final int number = 3;
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Integer.toString(number));
}
}.start();
}
}
太棒了。现在,问题是,最终修饰符只能阻止我更改变量指向的对象,但我可以毫无问题地更改对象。如果进行了“复制”,则不应反映对对象内容的更改。因此,问题是,为什么以下代码有效?
import java.util.HashMap;
import java.util.Map;
public class StretchingClosure {
public static void main(String[] args) {
new StretchingClosure().doStuff();
}
public void doStuff() {
final Map<String, String> map = new HashMap<String, String>();
map.put("animal", "cat");
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(map.get("animal")); // Outputs dog See (1)
}
}.start();
map.put("animal", "dog");
}
}
毋庸置疑,我遗漏了一些东西,或者我对编译器处理这些情况的方式过于简单。有人可以赐教我吗?
(1):正如@trashgod所指出的,在大多数平台上大部分时间输出都是正确的,但由于缺乏同步而无法保证。这对于这个例子来说已经足够了,但总的来说是不好的做法。
答案 0 :(得分:4)
不要将变量与对象混淆:来自局部变量的引用确实已复制,但它仍然引用同一个对象,在您的情况下是地图。有一个广为人知的习惯用于解决final
限制,涉及数组:
final int[] x = {1};
... use in an anonymous instance...
System.out.println(x[0]);
答案 1 :(得分:1)
Java与常规方法参数的作用相同:
方法参数通过引用值传递,因此虽然您无法更改对象本身,但如果它是可变的并提供了改变其内部状态的方法,则可以更改该状态。您无法更改字符串,但您可以更改集合中的项目,例如。
引用按值传递=复制引用。对象本身不是。
答案 2 :(得分:1)
评论// Outputs dog
具有误导性,因为在大多数平台上大多数情况下它只是真实的。一秒钟就有足够的时间来更新初始线程上的Map
,但除非正确同步对共享数据的访问,否则不会保证匿名线程中更新值的可见性。有关java.util.concurrent
中相关功能的精彩摘要,请参阅Memory Consistency Properties。
答案 3 :(得分:0)
匿名类不会获取变量的副本,而是获取对象引用的副本,这就是为什么在1s后你获得了在匿名类之外更改的“正确”值。