Newsletter Developpez.com

Inscrivez-vous gratuitement au Club pour recevoir
la newsletter hebdomadaire des développeurs et IT pro

Les membres statiques, finaux et non immuables

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Les membres publiques, statiques et finaux sont souvent employés pour fournir des instances prédéfinies d'une classe. La classe java.awt.Color est un excellent exemple de cette technique : Color.RED, Color.BLUE, etc. sont des instances de Color avec des composantes RVB prédéfinies.

Cette technique est excellente et servait jusqu'à Java 1.5 à définir des énumérations. Elle souffre néanmoins d'un grave défaut lié à la mutabilité des objets. Si vous déclarez de telles instances à partir d'une classe non-immuable, vous vous réservez de longues sessions de debugging ardu. (Plus d'informations sur les classes immuables) Le concept de classe immuable étant souvent mal maîtrisé, la technique des membres publiques, statiques et finaux l'est également.

Prenons un exemple très simple :

 
Sélectionnez

public class Name {
  public static final Name FENX = new Name("fenx");
  public static final Name JOJOLAPIN = new Name("jojolapin");

  private String name;

  public Name(String name) {
    this.name = name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return this.name;
  }
}

Je suis persuadé que vous avez déjà vu de nombreuses classes ainsi rédigées. Il se peut même que vous soyez le forban responsable d'une telle maladresse ! Nos instances constantes sont très fragiles :

 
Sélectionnez

Name fenx = Name.FENX;
fenx.setName("Pifi");
System.out.println(Name.FENX);

Je vous épargne la copie du résultat depuis ma console car vous devez avoir à présent compris que FenX est devenu Pifi. Une technique souvent utilisée pour pallier ce problème est l'utilisation d'une interface.

Les exemples suivants reposent sur de vraies classes du projet SwingX. Richard Bair et moi-même avons récemment introduit l'API des painters (demo) et nous souhaitons proposer des painters pré-construits et utilisables tels quels. Un painter est défini par l'interface org.jdesktop.swingx.painter.Painter qui définit une seule et unique méthode, paint(Graphics2D, JComponent). En pratique tous nos painters héritent de la classe abstraite AbstractPainter qui fournit une API assez complète pour gérer la qualité, le clipping, un cache, etc.

Pour implémenter nos painters par défaut, notre premier réflexe a été de déclarer des constantes publiques :

 
Sélectionnez

public static final Painter GLOSSY_STRIPES = new CompoundPainter(
    new PinstripePainter(), new GlossPainter());

Cette implémentation semble parfaitement sûre à première vue car l'interface Painter ne permet pas de muter les instances. C'est bien mal connaître notre volonté de trouver des utilisations tordues. Nous avons heureusement immédiatement identifié un grave problème :

 
Sélectionnez

((CompoundPainter) Painter.GLOSSY_STRIPES).setPainters(
    new MattePainter(Color.RED));

Après exécution de cette ligne, GLOSSY_STRIPES ne dessine plus un reflet sur un fond de lignes obliques, mais un simple aplat de couleur rouge. La puissance du transtypage est sans limite. Comment prévenir une telle situation ? En utilisant le design pattern décorateur et la notion de composition si chère a la POO :

 
Sélectionnez

class ImmutablePainter implements Painter {
  private final Painter painter;

  ImmutablePainter(final Painter painter) {
    if (painter == null) {
      throw new IllegalArgumentException("Null painter.");
    }
    this.painter = painter;
  }

  public void paint(Graphics2D g2, JComponent c) {
    this.painter.paint(g2, c);
  }
}

// ...

public static final Painter GLOSSY_STRIPES = new ImmutablePainter(
    new CompoundPainter(new PinstripePainter(), new GlossPainter()));

ImmutablePainter n'est pas réellement immuable car le client créant l'instance conserve une référence sur le painter passé en paramètre et peut le modifier plus tard. Néanmoins, notez que cette classe est package-private, donc accessible uniquement par notre API. Cela signifie que nous pouvons garantir, par contrat, l'immuabilité de nos instances. Notez également la redéfinition de la constante GLOSSY_STRIPES. Celle-ci ne peut plus être convertie en une implémentation de Painter car ImmutablePainter est inaccessible. Nos instances sont maintenant en sûreté.

Notez que cette technique est également indispensable si vous utilisez une fabrique à la place de constantes publiques. Le cas de la fabrique est néanmoins un peu différent car vous pouvez retourner une copie de vos instances. Mais cela signifie que que vos classes doivent supporter l'interface Cloneable, qui apporte son propre lot d'ennuis. Quoi qu'il en soit, ces exemples mettent en évidence l'intérêt des classes immuables.

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