2016-02-04 1 views
5

Cette self-answered question a été inspirée par Variable 'snackbar' might not have been initialized. J'ai senti qu'il y avait plus de détails qui seraient mieux ajoutés séparément de cette question spécifique."L'exemple de variable n'a peut-être pas été initialisé" dans la classe anonyme

Pourquoi le code suivant ne peut-il pas être compilé?

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

erreur de compilation:

error: variable example might not have been initialized 

Répondre

8

Cela se produit en raison de la façon que les classes anonymes sont mises en œuvre. Vous pouvez le voir si vous faites une légère modification du code puis décompiler:

final Runnable other = null; 
    final Runnable example = new Runnable() { 
     @Override 
     public void run() { 
     System.out.println(other); 
     } 
    }; 

à savoir faire la classe anonyme se réfèrent à une variable locale différente. Cela va maintenant compiler; nous pouvons décompiler en utilisant javap et voir l'interface de la classe anonyme:

final class Example$1 implements java.lang.Runnable { 
    final java.lang.Runnable val$other; 
    Example$1(java.lang.Runnable); 
    public void run(); 
} 

(Example$1 est le nom par lequel Java fait référence interne à la classe anonyme).

Ceci montre que le compilateur a ajouté un constructeur à la classe anonyme qui prend un paramètre Runnable; il a également un champ appelé val$other. Ce nom de ce champ devrait indiquer que ce champ est lié à la variable locale other.

Vous pouvez creuser dans le bytecode plus loin, et voir que ce paramètre est affecté à 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 

Alors, que cela montre est la façon que les classes anonymes accèdent aux variables de leur portée englobante: ils sont simplement passé la valeur au moment de la construction.

Cela devrait montrer, espérons-le, pourquoi le compilateur vous empêche d'écrire du code comme celui de la question: il doit être capable de passer la référence au Runnable à la classe anonyme pour le construire. Cependant, la façon dont Java évalue le code suivant:

final Runnable example = new Runnable() { ... } 

est d'évaluer pleinement la droite d'abord, puis l'affecter à la variable du côté gauche. Cependant, il a besoin de la valeur de la variable sur la droite pour passer dans le constructeur généré de Runnable$1:

final Runnable example = new Example$1(example); 

Ce example n'a pas été déclaré précédemment n'est pas un problème, puisque ce code est sémantiquement identique à:

final Runnable example; 
example = new Example$1(example); 

si l'erreur que vous obtenez est pas que la variable ne peut être résolue - mais, example n'a pas été attribué une valeur avant qu'elle ne soit utilisée comme argument du constructeur, d'où la erreur de compilation.


On pourrait faire valoir que cela est tout simplement un détail de mise en œuvre: il ne devrait pas question que l'argument doit être passé dans le constructeur, car il n'y a aucun moyen que la méthode run() peut être invoquée avant la affectation.

En fait, ce n'est pas vrai: vous pouvez invoquer run() avant la cession, comme suit:

final Runnable example = new Runnable() { 
    Runnable runAndReturn() { 
    run(); 
    return this; 
    } 

    @Override public void run() { 
    System.out.println(example); 
    } 
}.runAndReturn(); 

Si référence à example dans la classe anonyme ont été autorisés, vous seriez en mesure d'écrire cela. Par conséquent, se référer à cette variable est interdit.

+0

Vraiment sympa! Avoir la classe 'Example $ 1' décompilée montre à peu près _why_ devrait' other' être 'final'. Si ce n'était pas le cas, le 'Example $ 1' pourrait potentiellement traiter une copie périmée de' other', ce qui serait ... étrange, c'est le moins qu'on puisse dire. –

4

Vous pouvez utiliser "ce" pour éviter l'erreur de compilation:

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