从外部方法访问变量

Access variables from outer methods

我正在研究 Java 中的内部 Class,我有一个与外部方法中的变量引用相关的问题。例如,我有一个源代码来计算在排序过程中调用了多少次 compareTo() 方法:

        int counter = 0;
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter + " comparisons");

执行源码可以看到counter++的使用存在错误。为了解决这个问题,有人告诉我应该这样改:

        int[] counter = new int[1];
        Date[] dates = new Date[100];
        for(int i = 0; i < dates.length; i++)
        {
            dates[i] = new Date()
            {
                public int compareTo(Date other)
                {
                    counter[0]++;
                    return super.compareTo(other);
                }
            };
        }
        Arrays.sort(dates);
        System.out.println(counter[0] + " comparisons");

我很困惑,这两个代码之间有什么区别,这个错误的原因和解决方法是什么?

您正在创建可以 'travel' 的代码片段。 {} 中属于您的 new Date() 声明的代码不在您编写它的地方 运行;它附加到您创建的这个日期对象,并与之一起使用。这个日期对象可以旅行:它可以存储在一个字段中。也许是 运行 18 天后,在一个完全不同的线程中。 VM 不知道,因此需要为此做好准备。

假设它确实存在:您的 'counter' 变量会发生什么?

通常情况下,局部变量会存储 'on the stack' 并在方法退出时销毁。但在那种情况下,我们会破坏您的旅行代码仍然可以访问的变量,那么这意味着什么,从现在起 18 天,当您的日期 compareTo 代码被调用时?

假设 VM 静默 'upgrades' 变量;它不是像往常一样在堆栈上声明它,而是在堆上声明它,以便变量可以在方法退出后继续存在。

好的。如果在另一个线程中调用 compareTo 怎么办?现在是否可以将局部变量标记为 'volatile'?是否可以声明,在 java 中,即使局部变量也可能显示竞争条件?

这是一个判断电话;由语言设计者决定的东西。

Java 的语言设计者决定反对 静默升级到堆,反对 允许本地人可能受到 multi-thread访问。

因此,您在可以 'travel'* 的任何代码块中访问的任何局部变量必须声明 [A] final 或 [B] 就像它本可以那样,其中case java 会默默地为你完成它。

变化是 counter,变量本身,在第二个片段中没有改变:它是对数组的引用,并且该引用永远不会改变.实际上,您自己添加了间接级别和堆访问:堆上存在数组。

就其价值而言,我发现 AtomicX 的用法更具可读性。因此,如果您需要一个可在移动代码中修改的 int,请不要执行 new int[1];做 new AtomicInteger。如果您需要可修改的字符串,请使用 new AtomicReference<String>(),而不是 new String[1]

注意:是的,在 this 特定代码中,仅使用计数器变量,即使是排序操作,在该方法中,计数器变量也可以在该方法中消失结束,但编译器并没有进行那种极其深入的分析来弄清楚这一点,它使用了更简单的规则:想从 'travelling' 代码中的外部 scipe 访问本地变量?不允许 - 除非它(实际上)是最终的。

*) 移动代码是方法本地或匿名 class 定义中的任何内容,以及 lambda 中的任何内容。所以:


void method() {
    class MethodLocalClassDef {
        // anything here is considered 'travelling'
    }

    Object o = new Object() {
        // this is an anonymous class def,
        // and anything in here is 'travelling'
    };

    Runnable r = () -> {
        // this is a lambda, and considered 'travelling'
    };
}