Dzieje się tak ze względu na sposób, że anonimowe klasy są wdrażane. Można to zobaczyć, jeśli się nieznaczne zmiany w kodzie, a następnie dekompilować:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
to zrobić anonimowy klasy odnoszą się do innej zmiennej lokalnej. To teraz się skompiluje; możemy dekompilować użyciu javap
i zobaczyć interfejs anonimowej klasy:
final class Example$1 implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example$1(java.lang.Runnable);
public void run();
}
(Example$1
jest imię, którym Java wewnętrznie odnosi się do anonimowej klasy).
Pokazuje to, że kompilator dodał konstruktor do anonimowej klasy, która przyjmuje parametr Runnable
; ma również pole o nazwie val$other
. Nazwa tego pola powinna wskazywać, że to pole jest powiązane ze zmienną lokalną other
.
Można kopać do kodu bajtowego dalej i zobaczyć, że parametr ten jest przypisany do val$other
:
Example$1(java.lang.Runnable);
Code:
0: aload_0
// This gets the parameter...
1: aload_1
// ...and this assigns it to the field val$other
2: putfield #1 // Field val$other:Ljava/lang/Runnable;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
Więc, co pokazuje to jest sposób, anonimowe klas uzyskać dostęp do zmiennych z ich zakresem załączając: są po prostu przekazał wartość w czasie budowy.
To powinno pokazać, dlaczego kompilator powstrzymuje cię przed napisaniem kodu takiego jak ten w pytaniu: aby móc go skonstruować, musi przekazać odwołanie do Runnable
anonimowej klasie. Jednak sposób, że Java ocenia następujący kod:
final Runnable example = new Runnable() { ... }
jest, aby w pełni ocenić prawą stronę, a potem przypisać ją do zmiennej po lewej stronie. Jednak potrzebuje wartość zmiennej po prawej stronie, aby przejść do wygenerowanego konstruktora Runnable$1
:
final Runnable example = new Example$1(example);
To example
nie została wcześniej zadeklarowana nie jest problemem, ponieważ kod ten jest semantycznie identyczne:
final Runnable example;
example = new Example$1(example);
więc błąd, że nie można dostać to, że zmienna nie może być rozwiązany - jednak example
nie została przypisana wartość, zanim zostanie użyta jako argument do konstruktora, stąd błąd kompilatora.
Można twierdzić, że jest to po prostu realizacja szczegół: to nie powinno mieć znaczenia, że argument musi być przekazany do konstruktora, ponieważ nie ma możliwości, że metoda run()
można powoływać się przed kontrolą zadanie.
Właściwie, to nie jest prawda: można powołać run()
przed przypisaniem, co następuje:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
Jeśli powołując się example
wewnątrz anonimowej klasy pozwolono, byłbyś w stanie to napisać. Dlatego odwołanie się do tej zmiennej jest niedozwolone.
bardzo miło! Posiadanie zdekompilowanej klasy 'Example $ 1' pokazuje, że _why_ powinien" inny "być" końcowy ". Gdyby tak nie było, "Przykład 1 $" mógł potencjalnie zajmować się przestarzałą kopią "innego", co byłoby ... dziwne, delikatnie mówiąc. –