Constructeurs et méthodes exportées

Romain Guy (Gfx)

- Titre : Constructeurs et méthodes exportées

- Auteur : Romain Guy (Gfx)

- Parution : 2 août 2006

- Licence : Cette création est mise à disposition sous un contrat Creative Commons (Paternité - Partage des Conditions Initiales à l'Identique).

Sommaire

♪ 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.