Как Java реализует закрытие внутренних классов? - PullRequest
23 голосов
/ 10 мая 2010

В Java анонимный внутренний класс может ссылаться на переменные в своей локальной области видимости:

public class A {
    public void method() {
        final int i = 0;

        doStuff(new Action() {
            public void doAction() {
                Console.printf(i);   // or whatever
            }
        });
    }
}

Мой вопрос: как это на самом деле реализовано? Как i попадает в анонимную внутреннюю реализацию doAction и почему она должна быть final?

Ответы [ 3 ]

16 голосов
/ 10 мая 2010

Локальные переменные (очевидно) не разделяются между различными методами, такими как method() и doAction() выше. Но поскольку он окончательный, в этом случае не может произойти ничего «плохого», поэтому язык по-прежнему допускает это. Однако компилятор должен сделать что-то умное с ситуацией. Давайте посмотрим на то, что javac производит:

$ javap -v "A\$1"           # A$1 is the anonymous Action-class.
...
final int val$i;    // A field to store the i-value in.

final A this$0;     // A reference to the "enclosing" A-object.

A$1(A, int);  // created constructor of the anonymous class
  Code:
   Stack=2, Locals=3, Args_size=3
   0: aload_0
   1: aload_1
   2: putfield #1; //Field this$0:LA;
   5: aload_0
   6: iload_2
   7: putfield #2; //Field val$i:I
   10: aload_0
   11: invokespecial #3; //Method java/lang/Object."<init>":()V
   14: return
   ...
public void doAction();
  Code:
   Stack=2, Locals=1, Args_size=1
   0: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: getfield #2; //Field val$i:I
   7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V
   10: return

Это на самом деле показывает, что

  • превратил переменную i в поле,
  • создал конструктор для анонимного класса, который принял ссылку на A объект
  • , к которому он позднее получил доступ в методе doAction().

(Примечание: мне пришлось инициализировать переменную на new java.util.Random().nextInt(), чтобы она не оптимизировала много кода.)


Подобное обсуждение здесь

метод local innerclasses, обращающийся к локальным переменным метода

11 голосов
/ 10 мая 2010

Компилятор автоматически генерирует конструктор для вашего анонимного внутреннего класса и передает вашу локальную переменную в этот конструктор.

Конструктор сохраняет это значение в переменной класса (поле), также называемой i, которая будет использоваться внутри «замыкания».

Почему это должно быть окончательным? Что ж, давайте рассмотрим ситуацию, в которой это не так:

public class A {
    public void method() {
        int i = 0; // note: this is WRONG code

        doStuff(new Action() {
            public void doAction() {
                Console.printf(i);   // or whatever
            }
        });

        i = 4; // A
        // B
        i = 5; // C
    }
}

В ситуации A поле i из Action также необходимо изменить, давайте предположим, что это возможно: ему нужна ссылка на объект Action.

Предположим, что в ситуации B этот экземпляр Action является сборщиком мусора.

Теперь в ситуации C: ему требуется экземпляр Action для обновления переменной класса, но значение GCed. Нужно «знать», что это GCed, но это сложно.

Таким образом, чтобы упростить реализацию виртуальной машины, разработчики языка Java сказали, что она должна быть окончательной, чтобы у виртуальной машины не было способа проверить, не исчез ли объект, и гарантировать, что переменная не была изменена. и что ВМ или компилятору не нужно хранить ссылки на все случаи использования переменной внутри анонимных внутренних классов и их экземпляров.

3 голосов
/ 10 мая 2010

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

См. Java Final - загадочная загадка для более подробной информации.

...