A chaque fois que je crée une nouvelle classe, je me demande si je dois forcer la valeur par défaut des variables d’instance. La plupart du temps je me dis “je ne veux pas dépendre d’un comportement par défaut, je vais forcer explicitement la valeur par défaut de la variable d’instance, ce sera plus lisible”. C’est peut-être discutable mais je n’ai jamais eu l’occasion d’en discuter avec des java-gurus, ni d’ailleurs de réellement me poser des questions sur le bien-fondé et la justesse de ce choix.
Jusqu’à ce jour. Je viens de découvrir (shame on me?) qu’il est parfois nécessaire de ne pas explicitement forcer la valeur par défaut.
1er cas:
public class MotherClass {
public MotherClass(String… args) {
System.out.println(“MotherClass(): [BERORE CALL TO PREPARE] this:” + this);
prepare(args);
System.out.println(“MotherClass(): [AFTER CALL TO PREPARE] this:” + this);
}
public void prepare(String… args) {this.member0_1 = args[0];
this.member0_2 = args[1];
System.out.println(“MotherClass.prepare(): this:” + this);
}
public String toString() {
return ”MotherClass <member0_1=” + member0_1 + ”, member0_2=” + member0_2 + ”>”;
}
protected String member0_1;
protected String member0_2 = ”initialized”;
public static void main(String[] args) {
MotherClass mc = new MotherClass(new String[] { ”first”, ”second” });
System.out.println(“main(): mc:” + mc);
}
}
Si on lance le main, ça donne:
MotherClass(): [BERORE CALL TO PREPARE] this:MotherClass <member0_1=null, member0_2=initialized>
MotherClass.prepare(): this:MotherClass <member0_1=first, member0_2=second>
MotherClass(): [AFTER CALL TO PREPARE] this:MotherClass <member0_1=first, member0_2=second>
main(): mc:MotherClass <member0_1=first, member0_2=second>
Dans ce cas, rien de particulier, la classe se comporte comme on peut s’y attendre, que la variable d’instance ait été initialisée (member0_2) ou pas (member0_1).
Etendons maintenant cette classe:
public class ClassThatExtends extends MotherClass {
public ClassThatExtends(String… args) {
super(args);
System.out.println(“ClassThatExtends(): [AFTER super()] this:” + this);
}
public void prepare(String… args) {
if (args.length == 4) {
this.member0_1 = args[0];
this.member0_2 = args[1];
this.member1_1 = args[2];
this.member1_2 = args[3];
}
System.out.println(“ClassThatExtends.prepare(): this:” + this);
}
public String toString() {
return ”ClassThatExtends <member0_1=” + member0_1 + ”, member0_2=” + member0_2
+ ”, member1_1=” + member1_1 + ”, member1_2=” + member1_2 + ”>”;
}
private String member1_1;
private String member1_2 = ”initialized”;public static void main(String[] args) {
ClassThatExtends cte = new ClassThatExtends(new String[] { ”first”, ”second”, ”third”,
“fourth” });
System.out.println(“main(): cte:” + cte);
}
}
Si on lance le main:
MotherClass(): [BERORE CALL TO PREPARE] this:ClassThatExtends <member0_1=null, member0_2=initialized, member1_1=null, member1_2=null>
ClassThatExtends.prepare(): this:ClassThatExtends <member0_1=first, member0_2=second, member1_1=third, member1_2=fourth>
MotherClass(): [AFTER CALL TO PREPARE] this:ClassThatExtends <member0_1=first, member0_2=second,member1_1=third, member1_2=fourth>
ClassThatExtends(): [AFTER super()] this:ClassThatExtends <member0_1=first, member0_2=second,member1_1=third, member1_2=initialized>
main(): cte:ClassThatExtends <member0_1=first, member0_2=second, member1_1=third, member1_2=initialized>
WTF? member1_1 qui n’a pas été explicitement initialisée dans la classe fille a gardé la valeur fixée lors de l’appel du constructeur, par contre, member1_2 qui a été explicitement initialisée a été écrasée après avoir été valorisée dans le constructeur!
Les variables d’instance d’une classe “fille” sont donc initialisées APRES la chaîne des appels des constructeurs.
Dans le cas où la variable n’est pas explicitement initialisée lors de sa déclaration et que la variable est valorisée dans le constructeur, la valeur de la variable d’instance sera conservée. Dans le cas où la variable d’instance est explicitement initialisée lors de sa déclaration et qu’elle est valorisée dans le constructeur, la valeur est écrasée par la valeur d’initialisation spécifiée!
Fichtre! En voila donc un comportement pas forcément trivial… Si quelqu’un arrive à me trouver le bout de doc chez Java (ou ailleurs) que j’ai du louper, je suis preneur.