2015-03-13 1 views
3

Je programme java professionnellement depuis plus de dix ans. C'est l'un des bogues les plus bizarres que j'ai jamais essayé de retrouver. J'ai un membre privé, je l'initialise et ensuite il change à null tout seul.Comment mon membre privé se met-il à zéro?

public class MyObject extends MyParent 
{ 
    private SomeOtherClass member = null; 

    public MyObject() 
    { 
     super(); 
    } 

    public void callbackFromParentInit() 
    { 
     member = new SomeOtherClass(); 
     System.out.println("proof member initialized: " + member); 
    } 

    public SomeOtherClass getMember() 
    { 
     System.out.println("in getMember: " + member); 
     return member; 
    } 
} 

Sortie:

proof member initialized: [email protected] 
in getMember: null 

Si vous exécutez ce code, évidemment, il fonctionne correctement. Dans mon code actuel, il n'y a que ces trois occurrences (cinq si vous comptez les printlns) dans ce modèle exact.

Ai-je rencontré un bug dans la JVM? Sauf erreur de ma part, la classe parent ne peut pas interférer avec un membre privé, et peu importe ce que je mets entre les lignes de code que je vous ai montrées, je ne peux pas changer la valeur de membre sans utiliser l'identificateur ".

+2

Avez-vous un exemple de travail complet? – Sundae

+1

Quelle est la sortie de: MyObject foo = new MyObject(); foo.callbackFromParentInit(); foo.getMember(); – davidbuzatto

+0

Je ne peux pas poster de code de travail. Je peux vous dire que l'identifiant "member" est utilisé seulement cinq fois dans le code dans le motif comme indiqué. Il ne devrait pas être possible de changer en null sans que l'identifiant soit utilisé à l'intérieur de cette classe sur une autre ligne. –

Répondre

11

Cela se produit en raison de l'ordre dans lequel les variables membres sont initialisées et les constructeurs sont appelés. Vous appelez callbackFromParentInit() à partir du constructeur de la superclasse MyParent.Lorsque cette méthode est appelée, elle définit . Mais après cela, la partie de sous-classe de l'initialisation d'objet est effectuée, et l'initialiseur member est exécuté, ce qui définit member à null.

Voir, par exemple:

Dans ce constructeurs commande sont appelés et les champs sont initialisés est décrit dans paragraph 12.5 de la spécification du langage Java.

3

L'affectation de null au champ member se produit après l'exécution du constructeur parent.

0

La solution est de changer:

private SomeOtherClass member = null; 

à:

private SomeOtherClass member; 
+0

C'est intéressant, je n'ai évidemment jamais fait ce modèle d'inits auparavant. Cela semble être un ordre stupide. –

+2

Ok, cela résout le problème immédiat, mais la vraie solution est de ne pas appeler les méthodes substituables du constructeur de la superclasse. Voir [cette question] (http://stackoverflow.com/questions/3342784/using-abstract-init-function-in-abstract-classs-constructor). – Jesper

0

Jamais, jamais appeler une méthode non finale du constructeur de la superclasse.

Cela est considéré comme une mauvaise pratique, précisément parce qu'il peut entraîner des erreurs désagréables et difficiles à corriger, comme celle que vous subissez.

Effectue l'initialisation d'une classe X dans le constructeur de X. Ne comptez pas sur l'ordre d'initialisation de java pour les hiérarchies. Si vous ne parvenez pas à initialiser la propriété de classe, c'est-à-dire parce qu'elle a des dépendances, utilisez le générateur ou le modèle d'usine.

Ici, la sous-classe est remise à zéro l'attribut member à null, en raison de la superclasse et les constructeurs de sous-classe et l'ordre d'exécution des blocs de initialiseur, dans lequel, comme déjà mentionné, vous ne devriez pas compter.

Veuillez vous référer à this related question pour les concepts concernant les constructeurs, les hiérarchies et l'échappement implicite de la référence this.

Je ne peux penser à coller à un (peut-être incomplète) ensemble de règles/principes pour éviter ce problème et d'autres aussi bien:

  • appeler uniquement private méthodes à partir du constructeur

  • Si vous aimez l'adrénaline et souhaitez appeler les méthodes protected depuis le constructeur, faites-le, mais déclarez ces méthodes comme final, afin qu'elles ne puissent pas être remplacées par su bclasses

  • Ne jamais créer des classes internes dans le constructeur, soit anonyme, local, statique ou non statique

  • Dans le constructeur, ne passent pas directement this comme argument pour quoi que ce soit

  • Évitez toute combinaison transitive des règles ci-dessus, c'est-à-dire ne crée pas de classe interne anonyme dans une méthode private ou protected final invoquée depuis le constructeur

  • Utilisez le constructeur t o juste construire une instance de la classe, et laisser seulement initialiser les attributs de la classe, soit avec les valeurs par défaut ou avec les arguments fournis