За допомогою анонімних класів ви фактично оголошуєте "безіменний" вкладений клас. Для вкладених класів компілятор генерує новий окремий публічний клас з конструктором, який прийме всі змінні, які він використовує як аргументи (для "названих" вкладених класів, це завжди екземпляр оригінального / огороджувального класу). Це робиться тому, що середовище виконання не має поняття вкладених класів, тому необхідно здійснити (автоматичне) перетворення з вкладеного в окремий клас.
Візьмемо, наприклад, цей код:
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