2010-05-10 14 views
20

In Java una classe interna anonima può fare riferimento a variabili nel suo ambito locale:In che modo java implementa chiusure di classe interna?

public class A { 
    public void method() { 
     final int i = 0; 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 
    } 
} 

La mia domanda è come è questo effettivamente attuato? In che modo i arriva all'implementazione interna anonima doAction e perché deve essere final?

risposta

11

Il compilatore genera automaticamente un costruttore per la classe interna anonima e passa la variabile locale in questo costruttore.

Il costruttore salva questo valore in una variabile di classe (un campo), anch'essa denominata i, che verrà utilizzata all'interno della "chiusura".

Perché deve essere definitivo? Bene cerchiamo di esplorare la situazione in cui non è:

public class A { 
    public void method() { 
     int i = 0; // note: this is WRONG code 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 

     i = 4; // A 
     // B 
     i = 5; // C 
    } 
} 

Nella situazione Un campo i di Action ha anche bisogno di essere cambiato, supponiamo che questo è possibile: ha bisogno del riferimento all'oggetto Action.

Si supponga che nella situazione B questa istanza di Action sia Garbage-Collected.

Ora nella situazione C: ha bisogno di un'istanza di Action per aggiornare la sua variabile di classe, ma il valore è GCed. Ha bisogno di "sapere" che è GCed, ma è difficile.

Quindi per semplificare l'implementazione della VM, i progettisti del linguaggio Java hanno detto che dovrebbe essere definitivo in modo tale che la VM non abbia bisogno di un modo per verificare se un oggetto è andato e garantire che la variabile non sia modificato, e che la VM o il compilatore non deve mantenere il riferimento di tutti gli usi della variabile all'interno di classi interne anonime e delle loro istanze.

+0

In realtà, la variabile sintetizzata che contiene una copia della variabile non è denominata i. A seconda della versione del compilatore che stai usando, è "$ i" o "+ i". –

15

Le variabili locali sono (ovviamente) non condivise tra diversi metodi come method() e doAction() sopra. Ma dal momento che è definitivo, in questo caso non può accadere nulla di "cattivo", quindi il linguaggio lo consente comunque. Tuttavia, il compilatore deve fare qualcosa di intelligente riguardo alla situazione. Diamo un'occhiata a ciò che javac produce:

$ javap -v "A\$1"   # A$1 is the anonymous Action-class. 
... 
final int val$i; // A field to store the i-value in. 

final A this$0;  // A reference to the "enclosing" A-object. 

A$1(A, int); // created constructor of the anonymous class 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_0 
    1: aload_1 
    2: putfield #1; //Field this$0:LA; 
    5: aload_0 
    6: iload_2 
    7: putfield #2; //Field val$i:I 
    10: aload_0 
    11: invokespecial #3; //Method java/lang/Object."<init>":()V 
    14: return 
    ... 
public void doAction(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: getstatiC#4; //Field java/lang/System.out:Ljava/io/PrintStream; 
    3: aload_0 
    4: getfield #2; //Field val$i:I 
    7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V 
    10: return 

Questo dimostra in realtà che è

  • trasformato la variabile i in un campo,
  • creato un costruttore per la classe anonima, che ha accettato un riferimento per l'oggetto
  • a cui successivamente ha avuto accesso nel metodo doAction().

(Una nota a margine:. Ho dovuto inizializzare la variabile per new java.util.Random().nextInt() per evitare che l'ottimizzazione via un sacco di codice)


discussione simile qui

method local innerclasses accessing the local variables of the method

+2

Non ha nulla (molto) da fare con la filettatura. È semplicemente un effetto collaterale. Bella java disasm, btw: fornisce alcune informazioni importanti sul compilatore. – Pindatjuh

+0

Hai ragione. Io rivedrò. Grazie per il puntatore. – aioobe

+0

@Pindatjuh, Aggiornato il disastro ... ho realizzato che ottimizzava molto codice mentre il compilatore capiva che 'i' era sempre 0. – aioobe

3

L'istanza della classe locale (la classe anonima) deve mantenere una copia separata della variabile, in quanto potrebbe sopravvivere la funzione. Per non avere la confusione di due variabili modificabili con lo stesso nome nello stesso ambito, la variabile è forzata ad essere definitiva.

Vedere Java Final - an enduring mystery per ulteriori dettagli.

Problemi correlati