理解Java对“闭包”的近似

时间:2013-01-25 13:03:08

标签: java closures

所以,这就是我所理解的: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所指出的,在大多数平台上大部分时间输出都是正确的,但由于缺乏同步而无法保证。这对于这个例子来说已经足够了,但总的来说是不好的做法。

4 个答案:

答案 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后你获得了在匿名类之外更改的“正确”值。