← CertifHub
Java 6 juin 2026 · 3 min · par L'équipe CertifApp

Utiliser les sealed classes en Java 17 pour modéliser des hiérarchies fermées

Les sealed classes, introduites avec Java 17, offrent un contrôle fin sur les sous‑classes autorisées, idéal pour les modèles de domaine stricts.

Pourquoi les sealed classes ?

Dans de nombreux projets Java, il est fréquent de vouloir limiter l’ensemble des implémentations possibles d’une interface ou d’une classe abstraite. Avant Java 17, on utilisait souvent le mot‑clé abstract combiné à une documentation stricte, mais rien n’empêchait un développeur extérieur d’ajouter une classe non prévue. Les sealed classes résolvent ce problème en déclarant explicitement quelles classes peuvent en hériter. Le compilateur vérifie alors la conformité, ce qui renforce la sécurité du modèle et simplifie les analyses statiques.

Syntaxe et règles d’utilisation

  1. Déclaration : on ajoute le modificateur sealed à la classe ou à l’interface, suivi de la clause permits qui liste les sous‑classes autorisées.
  2. Sous‑classes : chaque type indiqué dans permits doit être déclaré final, sealed ou non‑sealed.
  3. Visibilité : les sealed classes sont limitées à leur module ou package, selon la visibilité choisie (public, package‑private, etc.).
  4. Compatibilité : le mécanisme fonctionne avec le système de modules (JPMS) mais n’est pas obligatoire ; il s’applique aussi aux projets classiques sans module.

Exemple de déclaration

public sealed class Shape permits Circle, Rectangle, Triangle {
    public abstract double area();
}

public final class Circle extends Shape {
    private final double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override public double area() { return Math.PI * radius * radius; }
}

public sealed class Rectangle extends Shape permits FilledRectangle {
    protected final double width, height;
    public Rectangle(double w, double h) { this.width = w; this.height = h; }
    @Override public double area() { return width * height; }
}

public final class FilledRectangle extends Rectangle {
    private final String color;
    public FilledRectangle(double w, double h, String color) { super(w, h); this.color = color; }
}

public non-sealed class Triangle extends Shape {
    private final double base, height;
    public Triangle(double b, double h) { this.base = b; this.height = h; }
    @Override public double area() { return 0.5 * base * height; }
}

Dans cet exemple, Shape ne peut être étendu que par les trois classes listées. Rectangle reste lui‑même sealed et autorise uniquement FilledRectangle. Triangle est déclaré non‑sealed, ce qui signifie qu’il peut être sous‑classé librement.

Cas d’usage concret : Modélisation d’un arbre d’expression

Supposons que l’on développe un mini‑interpréteur d’expression arithmétique. Chaque nœud de l’arbre représente une opération ou une valeur. Avec les sealed classes, on garantit que seuls les types d’opérations prévus (Add, Mul, Const) peuvent apparaître.

sealed interface Expr permits Const, Add, Mul {}

record Const(double value) implements Expr {}

record Add(Expr left, Expr right) implements Expr {}

record Mul(Expr left, Expr right) implements Expr {}

// Évaluation récursive
default double eval(Expr e) {
    return switch (e) {
        case Const c -> c.value();
        case Add a -> eval(a.left()) + eval(a.right());
        case Mul m -> eval(m.left()) * eval(m.right());
    };
}

Le switch exhaustif est possible parce que le compilateur sait que Expr ne pourra jamais être implémenté par un autre type. Ainsi, aucun default n’est requis, ce qui rend le code plus lisible et élimine les erreurs d’oubli.

Bonnes pratiques et pièges à éviter

  • Limiter le nombre de sous‑classes : un sealed class doit réellement restreindre le champ d’application. Si vous listez plus de dix sous‑types, considérez plutôt une hiérarchie traditionnelle.
  • Choisir le bon niveau de visibilité : exposez le sealed class en public uniquement si vous avez l’intention de le consommer hors du module. Sinon, privilégiez le package‑private pour éviter les fuites inutiles.
  • Ne pas mélanger sealed et abstract inutilement : sealed implique déjà une forme d’abstraction; ajouter abstract ne change rien et peut prêter à confusion.
  • Faire attention aux évolutions : ajouter une nouvelle sous‑classe nécessite de modifier la clause permits et de recompiler les clients. Planifiez ces changements dans votre feuille de route de versionnage.
  • Utiliser record lorsque cela convient : les records, combinés aux sealed interfaces, offrent une syntaxe très concise pour les DTO immuables, comme illustré dans l’exemple d’arbre d’expression.

En résumé, les sealed classes apportent une nouvelle dimension de contrôle sur les hiérarchies de types. Elles renforcent la lisibilité, permettent des switch exhaustifs et facilitent les vérifications statiques. Leur adoption, dès Java 17, constitue un levier précieux pour les développeurs qui préparent des certifications Java et qui souhaitent démontrer une maîtrise des concepts de conception avancée.

Envie d’aller plus loin avec CertifApp ?

Découvrir CertifApp