Как внутренние блоки в Java обращаются к локальным переменным, которые должны быть вне области видимости? (Как JVM обрабатывает конечные локальные переменные в Java) - PullRequest
3 голосов
/ 20 сентября 2011

В следующем коде:

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, установленным на ноль.

  2. m.aMethod();: вызов aMethod, который включает в себя копирование его переменной экземпляра stackVar в стек, а затем создание нового экземпляра класса Emp и назначение его globalEmp экземпляру.

  3. локальная переменная stackVar будет вытолкнута из стека, когда метод aMethod достигнет конца.

  4. m.globalEmp.doSomeThing();: функция doSomeThing будет вызываться для ранее созданного объекта, указанного в переменной globalEmp. и так как эта функция doSomeThing обращается к локальной переменной stackVar, которая предположительно не всплывает из кэша, она должна выдать ошибку, сообщающую, что.

Итак, как это действительно работает во время выполнения Java?

EDIT

  1. Поскольку среда выполнения создаст поверхностную копию окончательных локальных переменных (согласно ответам ниже), почему он не разрешает доступ и для нефинальных переменных?

  2. Можете ли вы предоставить мне какую-нибудь ссылку, касающуюся этого (в спецификации или на официальном официальном месте)?

Ответы [ 3 ]

4 голосов
/ 20 сентября 2011

Обычно анонимный внутренний класс генерирует конструктор, который принимает значения локальных переменных, на которые ссылается класс анонимного внутреннего класса.Сгенерированный код превращает это:

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

в:

globalEmp = new Main$1(stackVar);

Этот конструктор затем копирует значение в скрытые поля внутри сгенерированного анонимного класса.

Другими словами, он создает копию значения .Переменная, из которой получено исходное значение, не имеет значения после этого.

В случае, если вы на самом деле показали, это не понадобится, потому что 10 - это константа - но это general способ захвата переменных.

РЕДАКТИРОВАТЬ: Отвечая на ваше редактирование ...

Переменные должны быть окончательными, чтобы не было путаницы - если вы можете изменить их, некоторые разработчики могут ожидайте любых изменений, которые будут видны в анонимном классе, как они есть в других реализациях замыкания.

Что касается официальной документации по этому вопросу, то самое близкое, что я вижу в спецификации языка Java, это раздел 8.1.3 , хотя в нем не говорится о мотивации различных решений.

3 голосов
/ 20 сентября 2011

Анонимные внутренние классы (что определяет new Emp() { }) создадут копию любых final локальных переменных, на которые они ссылаются.Внутренне они будут иметь доступ только к этой копии, а не к самой переменной final.

Таким образом, код в анонимном внутреннем классе может «ссылаться» на локальную переменную еще долго после вызова метода, в котором она существовала..

Это также причина, по которой в анонимных внутренних классах можно получить доступ только к final локальным переменным: если бы вы могли сделать то же самое с не final локальными переменными, то этот трюк копирования был бы оченьочевидно (поскольку вы никогда не могли наблюдать измененное значение, только начальное на момент создания копии).

0 голосов
/ 20 сентября 2011

Это будет вытеснено из области видимости для метода; но поскольку переменная является конечной, машина сохраняет ссылку / копию на нее для класса Emp, созданного и сохраненного в globalEmp.

Пока снова не вызывается aMethod; значение int останется в памяти, так как на него все еще ссылаются в экземпляре Emp, хранящемся в экземпляре Main. После второго вызова экземпляр Emp удаляется вместе с int после сборки мусора.

Это также причина, по которой вы должны сделать переменную final, чтобы использовать ее в абстрактных внутренних классах; в противном случае указанная переменная может изменить свое содержимое (значение) и, таким образом, нарушить работу. Это можно сделать безопасно, так как конечный объект может быть только прочитан и никогда не может быть изменен:).

...