L'invocation depuis un constructeur de methodes exportees est une faute tres commune en Java, et plus generalement en POO. Je le sais tres bien puisque je commets cette faute, consciemment toutefois, tres regulierement. Avant de continuer, rappelons qu'une methode exportee est une methode publique, protegee ou package-private appartenant a une classe non final, c'est-a-dire une methode qu'une classe fille peut redefinir.
A premiere vue, invoquer une methode exportee depuis le constructeur semble parfaitement valide et meme signe d'une bonne conception puisqu'on ne duplique pas d'instructions :
public class BreakMe {
protected String name;
protected String cached;
public BreakMe(String name) {
setName(name);
}
public void setName(String name) {
this.name = name;
this.cached = name.toUpperCase();
}
public String getName() {
return cached;
}
}
Le code de cette classe tres simple conserve en memoire le nom passe en parametre ainsi qu'une copie en majuscules. La methode getName renvoie toujours la copie en majuscules pour forcer les clients de cette API a respecter la casse choisie. L'appel a setName dans le constructeur semble etre une bonne solution, surtout si d'autres constructeurs sont ajoutes par la suite. Le code de la methode n'aura pas a etre duplique dans chaque constructeur.
Cette classe est malheureusement dangereuse car elle peut mener a l'obtention d'objets a moitie initialise. Comment cela est-ce possible ? Je vous presente la diabolique classe fille :
public class Breaker extends BreakMe {
public Breaker(String name) {
super(name);
}
public void setName(String name) {
this.name = "Utah Boy";
}
public static void main(String... args) {
Breaker b = new Breaker("Gredin");
System.out.printf("la longueur du nom est de %s caracteres.",
b.getName().length());
}
}
Cette nouvelle classe etend la premiere pour imposer le nom, malgre le parametre. Le brouillon qui a redige cette classe n'a malheureusement pas lu le code source de la premiere, ou a ete induit en erreur par une mauvaise documentation, et ne realise que l'operation la plus evidente, l'assignement du champ name. Apres execution du constructeur, le champ cached n'a toujours pas ete initialise et le programme ci-dessus plante miserablement :
gfx@undead /cygdrive/d
$ java Breaker
Exception in thread "main" java.lang.NullPointerException
at Breaker.main(Breaker.java:12)
Cet exemple est bien sur artificiel et peut etre corrige dans la classe fille en invoquant super.setName("Utah Boy") mais il s'agit d'un cas tres simple. De nombreux bugs, parfois subtils, peuvent etre provoques par une conception similaire. La seule solution valable est donc d'interdire dans les constructeurs les appels a des methodes exportees. Rien ne vous empeche cependant de factoriser votre code : utilisez this(...) pour faire converger les constructeurs vers un seul et unique ou utilisez des methodes d'initialisations privees.
Cette news a été rédigée en écoutant Offspring - Rage Againt Myself