2010-05-10 14 views
20

En Java une classe interne anonyme peut se référer à des variables dans sa portée locale:Comment java implémente-t-il les fermetures internes?

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

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

Ma question est de savoir comment cela est effectivement mis en œuvre? Comment i obtient l'implémentation interne anonyme doAction, et pourquoi doit-il être final?

Répondre

11

Le compilateur génère automatiquement un constructeur pour votre classe interne anonyme et transmet votre variable locale à ce constructeur.

Le constructeur enregistre cette valeur dans une variable de classe (un champ), également appelée i, qui sera utilisée dans la "fermeture".

Pourquoi cela doit-il être définitif? Eh bien, nous allons explorer la situation où il est:

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 
    } 
} 

Dans la situation A champ i de Action doit également être changé, supposons que cela est possible: il a besoin de la référence à l'objet Action.

Supposons que dans la situation B, cette instance de Action est récupérée à la poubelle.

Maintenant dans la situation C: il a besoin d'une instance de Action pour mettre à jour sa variable de classe, mais la valeur est GCed. Il doit "savoir" que c'est GCed, mais c'est difficile.Donc, pour simplifier l'implémentation de la machine virtuelle, les concepteurs de langage Java ont dit qu'elle devrait être finale de sorte que la machine virtuelle n'ait pas besoin de vérifier si un objet est parti, et garantir que la variable n'est pas modifié, et que la machine virtuelle ou le compilateur n'a pas à conserver la référence de tous les usages de la variable dans les classes internes anonymes et leurs instances.

+0

En fait, la variable synthétisée qui contient une copie de la variable n'est pas nommée i. Selon la version du compilateur que vous utilisez, "$ i" ou "+ i". –

15

Les variables locales ne sont (évidemment) pas partagées entre différentes méthodes telles que method() et doAction() ci-dessus. Mais puisque c'est définitif, rien de "mauvais" ne peut arriver dans ce cas, donc le langage le permet toujours. Le compilateur doit cependant faire quelque chose d'intelligent sur la situation. Permet de jeter un oeil à ce javac produit:

$ 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 

Cela montre en fait que ce

  • tourné la variable i dans un champ,
  • a créé un constructeur de la classe anonyme, qui a accepté une référence à l'objet A
  • auquel il a ensuite accédé dans la méthode doAction().

(remarque:. Je devais initialiser la variable à new java.util.Random().nextInt() pour l'empêcher d'optimiser loin beaucoup de code)


discussion similaire ici

method local innerclasses accessing the local variables of the method

+2

Cela n'a rien (beaucoup) à faire avec le filetage. C'est simplement un effet secondaire. Nice java disasm, btw: donne un bon aperçu du compilateur. – Pindatjuh

+0

Vous avez raison. Je vais réviser. Merci pour le pointeur. – aioobe

+0

@Pindatjuh, Mis à jour le désas ... a réalisé qu'il optimise beaucoup de code car le compilateur a réalisé que 'i' était toujours 0. – aioobe

3

L'instance de classe locale (la classe anonyme) doit conserver une copie distincte de la variable, car elle peut dépasser la fonction. Afin de ne pas avoir la confusion de deux variables modifiables ayant le même nom dans la même portée, la variable doit être finale.

Voir Java Final - an enduring mystery pour plus de détails.