Les membres statiques, finaux et non immuables

Romain Guy (Gfx)

- Titre : Les membres statiques, finaux et non immuables

- Auteur : Romain Guy (Gfx)

- Parution : 1 août 2006

- Mise à jour : 10 novembre 2019

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

Sommaire

- I. Introduction

I - Introduction

Les membres publics, 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 débogage ardu. (Plus d'informations sur les classes immuables) Le concept de classe immuable étant souvent mal maîtrisé, la technique des membres publics, statiques et finaux l'est également.

Prenons un exemple très simple :

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 :

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 :

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 :

((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 limites. Comment prévenir une telle situation ? En utilisant le design pattern décorateur et la notion de composition si chère a la POO :

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