С анонимными классами вы фактически объявляете «безымянный» вложенный класс. Для вложенных классов компилятор создает новый автономный общедоступный класс с конструктором, который будет принимать все переменные, которые он использует в качестве аргументов (для «именованных» вложенных классов это всегда экземпляр исходного / включающего класса). Это сделано потому, что среда выполнения не имеет понятия вложенных классов, поэтому необходимо (автоматическое) преобразование из вложенного в автономный класс.
Возьмем, к примеру, этот код:
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 изменяется, экземпляр, на который она указывает, не изменяется. , и все другие ссылочные переменные, указывающие на него (например, в анонимном классе: Enclosing $ 1), не будут знать об этом. Это основная причина, по которой компилятор вынуждает вас объявить эти «общие» переменные как final, чтобы такое поведение не отражалось в вашем уже работающем коде.
Вот что происходит, когда вы используете переменную экземпляра внутри анонимного класса (это то, что вы должны сделать для решения вашей проблемы, переместите свою логику в метод «экземпляра» или конструктор класса):
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 (), вы увидите, что дважды напечатано «other hello», потому что теперь охватывающая переменная EnclosingClass $ 1 # сохранит ссылку к объекту класса, в котором он был объявлен, поэтому изменения любого атрибута этого объекта будут видны экземплярам EnclosingClass $ 1.
Для получения дополнительной информации по этому вопросу вы можете просмотреть это отличное сообщение в блоге (написанное не мной): http://kevinboone.net/java_inner.html
person
emerino
schedule
28.09.2014