Подтвердить что ты не робот

Как Java реализует закрытие внутренних классов?

В 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?

4b9b3361

Ответ 1

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

Конструктор сохраняет это значение в переменной класса (поле), также называемой 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 of Action, предположим, что это возможно: ему нужна ссылка на объект Action.

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

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

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

Ответ 2

Локальные переменные (очевидно) не разделяются между различными методами, такими как 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(), чтобы она не удаляла много кода.)


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

метод локальных внутренних классов, обращающихся к локальным переменным метода

Ответ 3

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

Подробнее см. Java Final - прочную тайну.