В 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
?
Компилятор автоматически создает конструктор для вашего анонимного внутреннего класса и передает вашу локальную переменную в этот конструктор.
Конструктор сохраняет это значение в переменной класса (поле), также названной 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
является Garbage-Collected.
Теперь в ситуации C: ему нужен экземпляр Action
, чтобы обновить свою переменную класса, но значение GCed. Ему нужно "знать", что оно GCed, но это сложно.
Поэтому, чтобы упростить реализацию VM, разработчики языка Java сказали, что переменная должна быть конечной, чтобы VM не нужно было проверять, исчез ли объект, и гарантировать, что переменная не будет изменена, и чтобы VM или компилятору не нужно было хранить ссылки на все случаи использования переменной внутри анонимных внутренних классов и их экземпляров.
Экземпляр локального класса (анонимный класс) должен поддерживать отдельную копию переменной, так как она может выходить за пределы жить функцией. Чтобы не путать две изменяемые переменные с одним и тем же именем в одной и той же области видимости, переменная должна быть окончательной.
См. Java Final - непреходящая тайна для получения более подробной информации.
Локальные переменные (очевидно) не используются разными методами, такими как 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 ()
, чтобы предотвратить оптимизацию большого количества кода.)
Аналогичное обсуждение здесь
локальные внутренние классы метода, обращающиеся к локальным переменным метода