【问题标题】:How do Inner blocks in Java access local variables which are supposed to be out-of-scope? (How the JVM treats final local variables in Java)Java中的内部块如何访问应该超出范围的局部变量? (JVM如何处理Java中的final局部变量)
【发布时间】:2011-11-21 04:34:57
【问题描述】:

在以下代码中:

public class Main
{
    Emp globalEmp;

    public void aMethod()
    {
        final int stackVar = 10;

        globalEmp = new Emp()
        {
            public void doSomeThing()
            {
                System.out.println("stackVar :" + stackVar);
            }
        };
    }

    public static void main(String[] args)
    {
        Main m = new Main();
        m.aMethod();
        m.globalEmp.doSomeThing();
    }
}

interface Emp{
    public void doSomeThing();
}

据我所知,将执行以下操作:

  1. Main m = new Main(); :将创建 Main 类的新实例,globalEmp 设置为 null。

  2. m.aMethod(); :调用 aMethod 包括将其实例变量 stackVar 复制到堆栈中,然后创建 Emp 类的新实例并将其分配给 globalEmp 实例。

  3. 当方法aMethod结束时,局部变量stackVar会被压出栈。

  4. m.globalEmp.doSomeThing(); :函数doSomeThing 将在globalEmp 变量指向的先前创建的对象上调用。由于这个函数doSomeThing 正在访问一个本地变量stackVar,它应该不会从缓存中弹出,它应该会抛出一些错误说明。

那么,Java 运行时究竟是如何工作的?

EDIT:

  1. 由于运行时将创建最终局部变量的 副本(根据下面的答案),为什么它也不允许访问非最终变量?

  2. 您能否提供一些关于该问题的链接(在规范或某些官方位置)?

【问题讨论】:

    标签: java memory-management jvm inner-classes callstack


    【解决方案1】:

    通常,匿名内部类会生成一个构造函数,用于获取在匿名内部类的类中引用的局部变量的值。生成的代码变成这样:

    globalEmp = new Emp() { ... };
    

    进入:

    globalEmp = new Main$1(stackVar);
    

    然后该构造函数将值复制到生成的匿名类中的隐藏字段中。

    换句话说,它创建了一个的副本。之后原始值来自的变量就无关紧要了。

    在您实际展示的情况下,它不需要,因为 10 是一个常量 - 但这是捕获变量的一般方式。

    编辑:响应您的编辑...

    变量必须是最终的,这样就不会造成混淆 - 如果您可以更改它们,一些开发人员可能期望任何更改在匿名类中都是可见的,就像在其他闭包实现中一样.

    关于这方面的官方文档,我在 Java 语言规范中看到的最接近的文档是 section 8.1.3,尽管它没有谈到各种决定背后的动机。

    【讨论】:

      【解决方案2】:

      匿名内部类(这是new Emp() { } 定义的)将创建它们引用的任何final 局部变量的副本。在内部,他们只会访问该副本,而不是 final 变量本身。

      这样,匿名内部类中的代码可以在局部变量存在的方法调用完成很久之后“引用”局部变量。

      这也是为什么只有final 局部变量可以在匿名内部类中访问的原因:如果你可以对非final 局部变量做同样的事情,那么这个复制技巧将非常明显(因为你永远无法观察到更改的值,只能观察到复制时的初始值)。

      【讨论】:

        【解决方案3】:

        它将被推到方法的范围之外;但由于变量是最终变量,机器会为在 globalEmp 中创建和存储的 Emp 类保留一个引用/副本。

        直到再次调用aMethod; int 值将保留在内存中,因为它仍然在存储在 Main 实例中的 Emp 实例中被引用。在第二次调用后,Emp 实例与 int 一起在垃圾收集后被删除。

        这也是您必须将变量设为 final 才能在抽象内部类中使用它的原因;否则被引用的变量可能会改变它的内容(值),从而破坏事物。这可以安全地完成,因为最终对象只能被读取,而不能被修改:)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-12-25
          • 2014-03-02
          • 1970-01-01
          • 1970-01-01
          • 2020-06-29
          • 2013-05-10
          • 2011-08-04
          • 2012-05-27
          相关资源
          最近更新 更多