♪ L'invocation depuis un constructeur de méthodes exportées est une faute très commune en Java, et plus généralement en POO. Je le sais très bien puisque je commets cette faute, consciemment toutefois, très régulièrement. Avant de continuer, rappelons qu'une méthode exportée est une méthode publique, protected ou package-private appartenant à une classe non final, c'est-à-dire une méthode qu'une classe fille peut redéfinir.
À première vue, invoquer une méthode exportée depuis le constructeur semble parfaitement valide et même 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 très simple conserve en mémoire le nom passé en paramètre ainsi qu'une copie en majuscules. La méthode getName renvoie toujours la copie en majuscules pour forcer les clients de cette API à respecter la casse choisie. L'appel a setName dans le constructeur semble être une bonne solution, surtout si d'autres constructeurs sont ajoutés par la suite. Le code de la méthode n'aura pas a être dupliqué dans chaque constructeur.
Cette classe est malheureusement dangereuse, car elle peut mener à l'obtention d'objets à moitié initialisés. Comment cela est-ce possible ? Je vous présente 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 étend la première pour imposer le nom, malgré le paramètre. Le brouillon qui a rédigé cette classe n'a malheureusement pas lu le code source de la première, ou a été induit en erreur par une mauvaise documentation, et ne réalise que l'opération la plus évidente, l'assignement du champ name. Après exécution du constructeur, le champ cached n'a toujours pas été initialisé et le programme ci-dessus plante misérablement :
gfx@undead /cygdrive/d
$ java Breaker
Exception in thread "main" java.lang.NullPointerException
at Breaker.main(Breaker.java:12)
Cet exemple est bien sûr artificiel et peut être corrigé dans la classe fille en invoquant super.setName(« Utah Boy ») mais il s'agit d'un cas très simple. De nombreux bugs, parfois subtils, peuvent être provoqués par une conception similaire. La seule solution valable est donc d'interdire dans les constructeurs les appels à des méthodes exportées. Rien ne vous empêche cependant de factoriser votre code : utilisez this(…) pour faire converger les constructeurs vers un seul et unique ou utilisez des méthodes d'initialisations privées.