2016-02-04 10 views
5

Questo self-answered question è stato ispirato da Variable 'snackbar' might not have been initialized. Sentivo che c'erano più dettagli che sarebbero stati meglio aggiunti separati da quella specifica domanda."L'esempio di variabile potrebbe non essere stato inizializzato" nella classe anonima

Perché il seguente codice non può essere compilato?

public class Example { 
    public static void main(String[] args) { 
    final Runnable example = new Runnable() { 
     @Override 
     public void run() { 
     System.out.println(example); // Error on this line 
     } 
    }; 
    } 
} 

errore di compilazione:

error: variable example might not have been initialized 

risposta

8

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.

+0

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. –

4

È possibile utilizzare "questo" per evitare l'errore di compilazione:

final Runnable example = new Runnable() { 
    @Override 
    public void run() { 
    System.out.println(this); // Use "this" on this line 
    } 
}; 
Problemi correlati