Constructeurs et méthodes exportées

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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.

A 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 :

 
Sélectionnez

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 :

 
Sélectionnez

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 :

 
Sélectionnez

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

Articles et tutoriels Java
L'essentiel de Java en une heure
L'API java.nio du JDK 1.4
Inversion de contrôle en Java
L'introspection
Le Java Community Process
Conception de tests unitaires avec JUnit
Les Strings se déchaînent
Présentation de SWT
La programmation réseau en Java avec les sockets
Du bon usage de l'héritage et de la composition
Les références et la gestion de la mémoire en Java
Constructeurs et méthodes exportées en Java
Les membres statiques, finaux et non immuables en Java
Les classes et objets immuables en Java
Comprendre et optimiser le Garbage Collector
Les principes de la programmation d'une interface graphique
Les opérateurs binaires en Java
Prenez le contrôle du bureau avec JDIC
Les Java Data Objects (JDO version 1.0.1)
La persistance des données avec Hibernate 2.1.8
Journalisation avec l'API Log4j
Java 5.0 et les types paramétrés
Les annotations de Java 5
Java 1.5 et les types paramétrés
Créer un moteur de recherche avec Lucene
Articles et tutoriels Swing
Threads et performance avec Swing
Rechercher avec style en utilisant Swing
Splash Screen avec Swing et Java3D
Drag & Drop avec style en utilisant Swing
Attendre avec style en utilisant Swing
Mixer Java3D et Swing
Articles et tutoriels Java Web
Redécouvrez le web avec Wicket
Cette création est mise à disposition sous un contrat Creative Commons (Paternité - Partage des Conditions Initiales à l'Identique).