С анонимными классами вы фактически объявляете «безымянный» вложенный класс. Для вложенных классов компилятор генерирует новый автономный открытый класс с конструктором, который будет принимать все переменные, которые он использует в качестве аргументов (для «именованных» вложенных классов это всегда экземпляр исходного / включающего класса). Это сделано потому, что среда выполнения не имеет понятия о вложенных классах, поэтому необходимо выполнить (автоматическое) преобразование из вложенного в автономный класс.
Возьмите этот код для примера:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Это не сработает, потому что это то, что компилятор делает изнутри:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
Исходный анонимный класс заменяется некоторым автономным классом, который генерирует компилятор (код не точный, но должен дать вам хорошее представление):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Как вы можете видеть, автономный класс содержит ссылку на общий объект, помните, что все в java передаются по значению, поэтому даже если переменная ссылки 'shared' в EnclosingClass изменяется, экземпляр, на который он указывает, не изменяется и все другие ссылочные переменные, указывающие на него (как, например, в анонимном классе: с вложением $ 1), не будут знать об этом. Это основная причина, по которой компилятор заставляет вас объявлять эти «общие» переменные как окончательные, чтобы этот тип поведения не превращался в уже запущенный код.
Вот что происходит, когда вы используете переменную экземпляра внутри анонимного класса (это то, что вы должны сделать, чтобы решить свою проблему, переместить свою логику в метод «экземпляра» или конструктор класса):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Это прекрасно скомпилируется, потому что компилятор изменит код, так что новый сгенерированный класс Enclosing $ 1 будет содержать ссылку на экземпляр EnclosingClass, где он был создан (это только представление, но оно должно помочь вам):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
Подобным образом, когда ссылочная переменная 'shared' в EnclosingClass переназначается, и это происходит до вызова Thread # run (), вы увидите, что "другой привет" напечатан дважды, потому что теперь вложенная переменная EnclosingClass $ 1 # будет сохранять ссылку объекту класса, в котором он был объявлен, поэтому изменения любого атрибута этого объекта будут видны экземплярам EnclosingClass $ 1.
Для получения дополнительной информации по этому вопросу, вы можете увидеть это отличное сообщение в блоге (не написано мной): http://kevinboone.net/java_inner.html