Z anonimowych klas, są faktycznie uznającej „bezimienny” zagnieżdżone klasy. W przypadku klas zagnieżdżonych kompilator generuje nową autonomiczną klasę publiczną z konstruktorem, który pobiera wszystkie zmienne, których używa jako argumentów (w przypadku "nazwanych" klas zagnieżdżonych jest to zawsze instancja klasy oryginalnej/obejmującej). Dzieje się tak dlatego, że środowisko wykonawcze nie ma pojęcia klas zagnieżdżonych, więc musi istnieć (automatyczna) konwersja z zagnieżdżonej do samodzielnej klasy.
Weźmy na przykład ten kod:
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);
}
}
To nie będzie działać, bo to co robi kompilator pod maską:
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);
}
Oryginalny klasy anonimowy jest zastąpiony przez jakiś standalone klasa generowana przez kompilator (kod nie jest dokładny, ale powinien dać Ci dobry pomysł):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Jak widać, klasa autonomiczna zawiera odwołanie do obiektu współużytkowanego, pamiętaj, że wszystko w java jest wartością typu pass-by-value, więc nawet jeśli zmienna referencyjna "shared" w EnclosingClass zostanie zmieniona, instancja, którą wskazuje, nie jest zmodyfikowane i wszystkie inne zmienne wskazujące na nią (takie jak w anonimowej klasie: Enclosing $ 1), nie będą tego świadome. Jest to główny powód, dla którego kompilator zmusza Cię do zadeklarowania tych "współdzielonych" zmiennych jako ostatecznych, aby tego typu zachowanie nie spowodowało przejścia do już działającego kodu.
To właśnie dzieje się, gdy używasz zmiennej instancji wewnątrz anonimowej klasy (to, co powinieneś zrobić, aby rozwiązać swój problem, przenieś swoją logikę do metody "instancji" lub konstruktora klasy):
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);
}
}
to kompiluje grzywny, ponieważ kompilator będzie zmodyfikować kod, tak aby nowe wygenerowane klasy Zamknięcie 1 $ trzymać referencję do instancji EnclosingClass gdzie została instancja (to tylko przedstawienie, ale powinno ci będzie):
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);
}
}
W ten sposób, gdy zmienna referencyjna "udostępniona" w EnclosingClass zostanie ponownie przypisana, a dzieje się to przed wywołaniem Thread # run(), zobaczysz "inne powitanie" wydrukowane dwukrotnie, ponieważ teraz EnclosingClass $ 1 # zawiera zmienną zachowa odwołanie do obiektu klasy, w którym zostało zadeklarowane, więc zmiany dowolnego atrybutu na tym obiekcie będą widoczne dla instancji klasy EnclosingClass $ 1.
Aby uzyskać więcej informacji na ten temat można zobaczyć ten excelent blogu (nie napisane przez mnie): http://kevinboone.net/java_inner.html
To, o co pytam, to jak uzyskać zmienną w zegarze, którą mogę ciągle aktualizować. – Ankur
@Ankur: prosta odpowiedź brzmi "Nie". Ale możesz osiągnąć pożądany efekt używając wewnętrznej klasy; zobacz odpowiedź @ petercardona. –