Ciò si verifica a causa del modo in cui le classi anonime sono implementate. Si può vedere questo se si effettua una leggera modifica al codice e poi decompilare:
final Runnable other = null;
final Runnable example = new Runnable() {
@Override
public void run() {
System.out.println(other);
}
};
vale a dire rendere la classe anonima fare riferimento a una variabile locale diverso. Questo ora verrà compilato; siamo in grado di decompilare utilizzando javap
e vedere l'interfaccia della classe anonima:
final class Example$1 implements java.lang.Runnable {
final java.lang.Runnable val$other;
Example$1(java.lang.Runnable);
public void run();
}
(Example$1
è il nome con cui Java si riferisce internamente alla classe anonima).
Questo mostra che il compilatore ha aggiunto un costruttore alla classe anonima che accetta un parametro Runnable
; ha anche un campo chiamato val$other
. Questo nome di questo campo dovrebbe suggerire che questo campo è correlato alla variabile locale other
.
È possibile scavare nel bytecode oltre, e vedere che questo parametro è assegnato a 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
Quindi, ciò che questo dimostra è il modo in cui le classi anonime accedere alle variabili dal loro ambito di inclusione: sono semplicemente passato il valore al momento della costruzione.
Questo dovrebbe mostrare spera perché il compilatore vi impedisce di scrivere codice come quello di cui alla domanda: ha bisogno di essere in grado di passare il riferimento alla Runnable
alla classe anonima per costruirlo. Tuttavia, il modo che Java valuta il seguente codice:
final Runnable example = new Runnable() { ... }
è quello di valutare pienamente il lato destro, e poi assegna alla variabile sul lato sinistro. Tuttavia, è necessario il valore della variabile sul lato destro per passare nel costruttore generato Runnable$1
:
final Runnable example = new Example$1(example);
Questo example
non è stato precedentemente dichiarato non è un problema, dal momento che questo codice è semanticamente identico a:
final Runnable example;
example = new Example$1(example);
modo l'errore che si ottiene non è che la variabile non può essere risolto - tuttavia, example
non è stato assegnato un valore prima che venga utilizzato come argomento al costruttore, da cui il errore del compilatore.
Si potrebbe sostenere che questo è semplicemente un dettaglio di implementazione: non dovrebbe avere importanza che l'argomento deve essere passato al costruttore, in quanto non v'è alcun modo che il metodo run()
può essere invocato prima della assegnazione.
In realtà, questo non è vero: è possibile richiamare run()
prima della cessione, come segue:
final Runnable example = new Runnable() {
Runnable runAndReturn() {
run();
return this;
}
@Override public void run() {
System.out.println(example);
}
}.runAndReturn();
Se riferendosi alla example
all'interno della classe anonima era permesso, si sarebbe in grado di scrivere questo. Quindi, il riferimento a tale variabile non è consentito.
Davvero bello! Avere la classe 'Esempio $ 1 decompilata 'mostra quasi _why_ dovrebbe' altro' essere 'finale'. Se non lo fosse, l'Esempio $ 1 potrebbe potenzialmente occuparsi di una copia non aggiornata di "altro", che sarebbe ... strano, per non dire altro. –