Enum ou constantes

Publié le · Mis à jour le

Le problème

Des statuts représentés par des constantes entières :

public static final int STATUT_EN_COURS  = 0;
public static final int STATUT_TERMINE   = 1;
public static final int STATUT_DEMISSION = 2;

void enregistrer(String nom, int statut) { ... }

enregistrer("Jean Casimir-Perier", STATUT_DEMISSION); // ✅
enregistrer("Jean Casimir-Perier", 42);               // 💥 compile sans broncher
enregistrer("Jean Casimir-Perier", MAX_RETRIES);      // 💥 compile aussi

Le compilateur voit des int, rien de plus. N'importe quel entier est accepté, les logs affichent 2 au lieu d'un libellé lisible, et ajouter un statut oblige à retrouver à la main chaque if concerné. Une constante est faite pour une valeur fixe ; un choix parmi un ensemble connu relève d'un enum.

Constante ou enum

static final int DUREE_SEPTENNAT = 7;       // ✅ une valeur
static final String SEPARATEUR = ";";        // ✅ une valeur

enum Statut { EN_COURS, TERMINE, DEMISSION } // ✅ un choix
Constante static final enum
Représente Une valeur (nombre, texte, seuil) Un choix parmi un ensemble fermé
Typage Celui de la valeur (int, String) Un type dédié
Valeur invalide possible Oui Non
Données et comportement Non Oui (champs, méthodes)
Affichage La valeur brute Le nom de la constante

Les constantes primitives et String sont intégrées (inlined) dans le bytecode des classes appelantes. Si leur valeur change, les classes qui les utilisent doivent être recompilées.

Un enum est une vraie classe

Chaque constante d'enum est une instance unique, créée au chargement de la classe. Elle peut porter des champs, un constructeur et des méthodes :

public enum Statut {
    EN_COURS("Mandat en cours", false),
    TERMINE("Mandat terminé", true),
    DEMISSION("Démission", true);

    private final String libelle;
    private final boolean definitif;

    Statut(String libelle, boolean definitif) { // constructeur implicitement private
        this.libelle = libelle;
        this.definitif = definitif;
    }

    public String libelle() { return libelle; }
    public boolean estDefinitif() { return definitif; }
}

Statut s = Statut.DEMISSION;
s.libelle();        // "Démission"
s.estDefinitif();   // true
s == Statut.DEMISSION; // ✅ == est sûr : une seule instance par constante

Comportement par constante

Quand chaque constante a sa propre logique, une méthode abstraite remplace le switch :

public enum Mandat {
    SEPTENNAT { int duree() { return 7; } },
    QUINQUENNAT { int duree() { return 5; } };

    abstract int duree();
}

Un enum peut aussi implémenter une interface, ce qui permet de l'utiliser comme stratégie.

Méthodes fournies

Méthode Rôle
values() Tableau de toutes les constantes, dans l'ordre de déclaration
valueOf("TERMINE") Constante à partir de son nom exact, sinon IllegalArgumentException
name() Le nom déclaré, stable
ordinal() La position dans la déclaration (voir plus bas)

Pour convertir une saisie externe, entoure valueOf ou passe par une recherche explicite :

static Optional<Statut> depuisLibelle(String libelle) {
    return Arrays.stream(Statut.values())
            .filter(s -> s.libelle().equalsIgnoreCase(libelle))
            .findFirst(); // ✅ pas d'exception sur une saisie inconnue
}

EnumMap et EnumSet

Le JDK propose deux collections optimisées pour les clés d'enum. En interne, elles indexent directement par position : un tableau pour EnumMap, un champ de bits pour EnumSet. Elles sont plus rapides et plus compactes que HashMap et HashSet, et itèrent dans l'ordre de déclaration.

Map<Statut, List<String>> parStatut = new EnumMap<>(Statut.class); // ✅
parStatut.computeIfAbsent(Statut.DEMISSION, k -> new ArrayList<>())
         .add("Jean Casimir-Perier");

Set<Statut> definitifs = EnumSet.of(Statut.TERMINE, Statut.DEMISSION); // ✅
Set<Statut> tous       = EnumSet.allOf(Statut.class);
Set<Statut> autres     = EnumSet.complementOf(EnumSet.of(Statut.EN_COURS));

Avec les streams, Collectors.groupingBy accepte une fabrique de map :

Map<Statut, List<President>> groupes = presidents.stream()
        .collect(Collectors.groupingBy(President::statut,
                 () -> new EnumMap<>(Statut.class),
                 Collectors.toList()));

Ne pas dépendre de ordinal()

ordinal() renvoie la position de la constante dans la déclaration. Cette position change dès qu'on insère, supprime ou réordonne une constante.

enum Statut { EN_COURS, TERMINE, DEMISSION }
// DEMISSION.ordinal() == 2

enum Statut { EN_COURS, INTERIM, TERMINE, DEMISSION }
// DEMISSION.ordinal() == 3 💥 toutes les données stockées avec 2 sont faussées

C'est un piège classique de persistance : avec JPA, @Enumerated stocke par défaut l'ordinal. Préfère le nom, ou mieux, un code explicite et stable :

@Enumerated(EnumType.STRING) // ✅ stocke "DEMISSION"
private Statut statut;

Si un code numérique est imposé (protocole, base existante), déclare-le comme champ de l'enum plutôt que de le déduire de la position.

Ce qu'il ne faut PAS faire

  • ❌ Modéliser un choix avec des int ou des String. Aucune vérification de type, aucune valeur interdite. Déclare un enum et type les paramètres avec lui :

    void enregistrer(String nom, Statut statut) { ... } // ✅
    
  • ❌ Persister ou transmettre ordinal(). Réordonner les constantes corrompt silencieusement les données. Utilise name(), EnumType.STRING ou un code dédié.

  • ❌ Appeler valueOf directement sur une saisie utilisateur. Une casse différente ou une faute de frappe lève une IllegalArgumentException. Passe par une méthode de recherche qui renvoie un Optional.

  • ❌ Utiliser HashMap<Statut, …> ou HashSet<Statut>. Ça fonctionne, mais EnumMap et EnumSet sont plus rapides, plus compacts et ordonnés.

  • ❌ Transformer un enum en fourre-tout mutable. Des champs non final modifiés à l'exécution créent un état global partagé par toute l'application. Garde les champs final.

  • ❌ Remplacer toutes les constantes par des enums. Un seuil, un délai ou un séparateur sont des valeurs, pas des choix : une constante static final reste le bon outil.

À retenir

  • Constante pour une valeur fixe, enum pour un choix parmi un ensemble fermé.
  • Un enum est une classe : champs final, constructeur, méthodes, comportement par constante.
  • EnumMap et EnumSet sont les collections naturelles pour les clés d'enum.
  • Ne stocke jamais ordinal() : utilise le nom ou un code explicite.
  • Compare les constantes avec ==, convertis les saisies avec une recherche tolérante.

Avis sur l'article

Pas encore de note : soyez le premier à donner votre avis.

Les avis sont publiés après validation par le modérateur.

Votre note