Les classes scellées en Java 19 : Restrictions d'héritage et exclusion des classes record
Java 19 introduit les classes scellées (Sealed Classes), permettant aux développeurs de contrôler précisément quelles classes peuvent hériter ou implémenter une classe parente. Grâce au modificateur sealed, une classe peut se déclarer comme "scellée" et spécifier explicitement les sous-classes autorisées à l'étendre. Ces sous-classes doivent être listées dans une clause permits et chacune doit être modifiée avec final, sealed ou non-sealed.
- Syntaxe de base et restrictions des classes scellées
Une classe scellée exige que toutes les sous-classes directes autorisées soient dans le même module et déclarent explicitement leur mode d'héritage. Par exemple :
public sealed abstract class Forme autorise Cercle, Rectangle, Triangle { }
final class Cercle extends Forme { }
final class Rectangle extends Forme { }
sealed class Triangle extends Forme autorise TriangleIsocele, TriangleEquilateral { }
Dans ce code, Forme est une classe scellée qui n'autorise que Cercle, Rectangle et Triangle à hériter d'elle. Cercle et Rectangle sont déclarés comme final, indiquant qu'ils ne peuvent plus être hérités, tandis que Triangle est sealed et limite davantage ses propres sous-types.
- Pourquoi les classes record ne sont pas autorisées à hériter des classes scellées par défaut
Bien que les classes record (record) soient sémantiquement des porteurs de données immuables et semblent adaptées pour être des sous-classes de classes scellées, la spécification Java exige que toutes les sous-classes d'une classe scellée apparaissent explicitement dans la clause permits. Si la classe parente ne liste pas la classe record dans permits, le compilateur génèrera une erreur, même si la classe record respecte les conditions d'héritage.
- Les classes scellées améliorent la sécurité des types en empêchant les extensions inconnues
- Les classes record peuvent être des sous-classes de classes scellées, mais doivent être explicitement autorisées
- Toutes les sous-classes doivent être dans le même module que la classe scellée parente
| Type de sous-classe | Permis d'hériter d'une classe scellée | Condition préalable |
|---|---|---|
| Classe finale | Oui | Dans la liste permits |
| Classe scellée | Oui | Dans la liste permits et elle-même scellée |
| Classe record | Oui | Doit apparaître dans permits |
- Fondements syntaxiques des classes scellées et record en Java 19
3.1 Définition des classes scellées et explication du mot-clé permits
Les classes scellées (Sealed Classes) sont une fonctionnalité importante introduite en Java 17 pour limiter la structure d'héritage des classes ou interfaces. En déclarant explicitement quelles sous-classes peuvent implémenter ou hériter d'une classe parente, elles améliorent l'encapsulation et la sécurité des types.
Rôle du mot-clé permits
Dans une classe scellée, le mot-clé permits sert à spécifier explicitement la liste des sous-classes autorisées à hériter de cette classe. Si elles ne sont pas listées explicitement, le compilateur déduira automatiquement les sous-classes directes.
public sealed interface Forme autorise Cercle, Rectangle, Triangle {
double aire();
}
Ce code définit une interface scellée Forme qui n'autorise que les classes Cercle, Rectangle et Triangle à l'implémenter. Chaque sous-classe autorisée doit être déclarée avec l'un des modificateurs final, sealed ou non-sealed pour perpétuer le contrôle de l'héritage.
Règles de contrainte pour un héritage valide
- Toutes les sous-classes listées dans permits doivent être visibles dans le même module
- Les sous-classes doivent définir explicitement leur comportement de fermeture : finir l'héritage (final), continuer le scellement (sealed) ou ouvrir l'extension (non-sealed)
- Il n'est pas permis d'avoir des sous-classes indirectes non déclarées
Ce mécanisme fournit une base de déduction de type fiable pour des fonctionnalités de langage avancées comme la correspondance de modèles (pattern matching).
3.2 Sémantique d'immuabilité et caractéristiques structurelles des classes record
Les classes record (Record) sont une fonctionnalité d'aperçu introduite en Java 14 visant à simplifier la définition de porteurs de données immuables. Leur sémantique centrale réside dans l'immuabilité explicite et la déclaration structurée de données.
Déclaration et implémentation automatique
L'utilisation du mot-clé record permet de déclarer une classe immuable contenant uniquement des champs. Le compilateur génère automatiquement le constructeur, les accesseurs, equals, hashCode et toString.
public record Point(int x, int y) {}
Le code ci-dessus est équivalent à écrire manuellement une classe contenant des champs privés finals, des accesseurs publics et des méthodes standard. Chaque champ de la liste de paramètres est par défaut final, garantissant que l'état de l'instance ne peut pas être modifié après sa création.
Comparaison des caractéristiques structurelles
| Caractéristique | Classe normale | Classe record |
|---|---|---|
| Mutabilité de l'état | Mutable | Immuable |
| equals/hashCode | Doit être implémenté manuellement | Généré automatiquement |
| Surcharge mémoire | Élevée (méthodes redondantes) | Faible (structure compacte) |
3.3 Analyse de la compatibilité entre les classes scellées et les classes record
Caractéristiques de base des classes scellées et record
Une classe sealed limite la hiérarchie d'héritage, n'autorisant que des sous-classes spécifiques pour l'extension. Une record est un porteur de données immuable qui génère automatiquement le constructeur, les accesseurs et les méthodes equals/hashCode. Leur combinaison permet de construire des types de données algébriques à la fois sûrs sur le typage et sémantiquement clairs.
Exemple d'implémentation de la compatibilité
public sealed abstract class Forme autorise Cercle, Rectangle {}
public record Cercle(double rayon) extends Forme {}
public record Rectangle(double largeur, double hauteur) extends Forme {}
Dans ce code, Cercle et Rectangle sont des sous-classes explicitement autorisées de Forme, implémentées sous forme de record pour encapsuler les données. Le compilateur force la vérification de toutes les branches, améliorant la sécurité de la correspondance de modèles.
Comparaison des avantages clés
- Fermeture des types : garantit que toutes les sous-classes sont explicitement défineis, empêchant les extensions accidentelles
- Simplicité des données : le
recordgère automatiquement la sémantique des valeurs, réduisent le code样板 - Support de la correspondance de modèles : combiné avec les expressions
switch, permet une couverture complète des branches
3.4 Mécanisme de vérification à la compilation et principes d'implémentation des restrictions d'héritage
Le mécanisme de vérification à la compilation garantit la sécurité des types et l'exécution correcte des règles d'héritage par une analyse statique. Les concepteurs du langage utilisent souvent l'arbre de syntaxe abstraite (AST) pour intercepter les comportements d'héritage illégaux dès la phase de compilation.
Processus de vérification à la compilation
Lors de l'analyse des définitions de classe, le compilateur vérifie les modificateurs et les relations d'héritage. Par exemple, une classe modifiée par final interdit l'héritage :
public final class SystemeSecurise {
// N'autorise pas la sous-classe
}
Si ce code est hérité, le compilateur déclenche une erreur lors de l'analyse syntaxique : *"Cannot inherit from final class"*.
Principes d'implémentation des restrictions d'héritage
- Le tableau de symboles enregistre les attributs modificateurs de la classe
- Lors de l'analyse sémantique, on compare si la classe parente est final
- Avant la génération du code intermédiaire, on rejette les structures d'héritage illégales
Ce mécanisme évite les surcharges à l'exécution en reportant les contraintes au stade du développement, améliorant la stabilité et la maintenabilité du système.
3.5 Pratique : Construction d'une hiérarchie de classes scellées et de ses sous-classes valides
Dans les langages orientés objet modernes, les classes scellées (sealed class) sont utilisées pour limiter la hiérarchie d'héritage d'une classe, en assurant la sécurité des types. En déclarant une classe de base comme scellée, on peut spécifier explicitement quelles sous-classes sont des implémentations valides.
Définition d'une classe scellée
sealed class Resultat
data class Succes(val donnees: String) : Resultat()
data class Erreur(val message: String) : Resultat()
object Chargement : Resultat()
Ce code définit une classe scellée Resultat dont les sous-classes sont limitées à Succes, Erreur et Chargement, déclarés dans le même fichier. Le compilateur peut effectuer une vérification d'exhaustivité.
Utilisation d'une classe scellée pour la correspondance de modèles
- Les classes scellées sont souvent combinées avec des expressions
when - Le compilateur peut vérifier si toutes les sous-classes sont couvertes
- Évite les erreurs à l'exécution dues à des branches omises
Ce mécanisme convient aux scénarios de gestion d'état, de réponses de requêtes réseau, etc., où des collections de types fermées sont nécessaires, améliorant la robustesse du code.
- Raisons de conception de l'exclusion des classes record
4.1 Génération automatique de code des classes record et risques d'héritage
Dans le développement Java moderne, les classes record (Record) simplifient la définition de porteurs de données immuables par génération automatique de code. Le compilateur génère automatiquement les constructeurs, les accesseurs, equals(), hashCode() et toString(), améliorant considérablement l'efficacité du développement.
Structure de base des classes record
public record Utilisateur(String nom, int age) {}
Le code ci-dessus est équivalent après compilation à une classe standard contenant un constructeur complet, des accesseurs et des méthodes. Les champs sont par défaut final et les instances sont immuables.
Risques associés à l'héritage
- Les classes record héritent implicitement de
java.lang.Recordet ne supportent pas l'héritage explicite d'autres classes - Une sous-classe ne peut pas maintenir la sémantique de cohérence avec la classe record
- Violation du contrat d'immuabilité des objets de valeur
Par conséquent, les classes record devraient éviter toute forme d'héritage pour garantir leur simplicité et leur sécurité en tant que porteurs de données.
4.2 Exigences de cohérence sémantique pour la correspondance de modèles et les porteurs de données
Dans la conception de systèmes de types, la correspondance de modèles non seulement dépend de la compatibilité structurelle, mais exige également que les porteurs de données aient une cohérence sémantique claire. Si les types ont le même nom mais des sémantiques différentes, cela peut entraîner des erreurs de conversion implicite.
Exemple de code d'alignement sémantique
type Temperature float64
type Distance float64
func Mesurer(temp Temperature) {
// N'accepte que le type Temperature, même si le type sous-jacent est identique
}
var t Temperature = 36.5
var d Distance = 100.0
Mesurer(t) // Correct
// Mesurer(d) // Erreur de compilation : type incompatible
Ce code isole la sémantique par des types personnalisés, empêchant l'utilisation accidentelle d'une distance pour un paramètre de température. Bien que les deux types aient le même type sous-jacent float64, le compilateur rejette le passage implicite en se basant sur le nom du type et la sémantique.
Principes clés de la sécurité des types
- La correspondance de modèles doit vérifier à la fois la structure et la sémantique
- Éviter de se fier uniquement au nom des champs ou au "duck typing"
- Utiliser le modèle de "nouveau type" (Newtype) pour renforcer l'expressivité contextuelle
4.3 Pratique : Analyse des erreurs de compilation lors de la tentative d'implémentation de l'héritage scellé avec des classes record
En Java, les classes record (record) sont par défaut finales et ne peuvent pas être héritées, ce qui les rend naturellement adaptées à la construction de porteurs de données immuables. Cependant, toute tentative d'extension d'une classe record déclenchera une erreur de compilation.
Exemple de code et erreur de compilation
record Point(int x, int y) {}
record PointColore(int x, int y, String couleur) extends Point(x, y) {} // Erreur de compilation
Dans ce code, PointColore tente d'hériter de Point, mais les classes record ont une sémantique implicite final qui interdit l'héritage. Le compilateur génère donc l'erreur : "illegal inheritance from sealed class".
Analyse des causes de l'erreur
- La conception des classes record vise l'immuabilité et l'orientation valeur, l'héritage violerait leur encapsulation
- À partir de Java 17, les classes record sont considérées comme "scellées" par défaut, interdisant les sous-classes dérivées
- Même si la syntaxe autorisait le délégation du constructeur, cela serait interdit sémantiquement
Ce mécanisme garantit la sécurité et la cohérence des classes record dans la correspondance de modèles et la modélisation de données.
- Alternatives et meilleures pratiques
5.1 Utilisation de classes normales au lieu des classes record pour implémenter l'héritage scellé
Avant l'introduction des classes record (record) en Java 16, l'héritage scellé était généralement implémenté à l'aide de classes normales combinées à un contrôle d'accès. En définissant explicitement des constructeurs et en limitant les droits d'héritage, les développeurs pouvaient simuler l'immuabilité et la fermeture des types des classes record.
Implémentation manuelle d'une classe scellée
L'utilisation du mot-clé final ou de constructeurs de niveau package privé peut empêcher les extensions externes :
public abstract class Forme {
private Forme() {} // Interdit l'instanciation externe
public static final class Cercle extends Forme {
public final double rayon;
public Cercle(double rayon) { this.rayon = rayon; }
}
public static final class Rectangle extends Forme {
public final double largeur, hauteur;
public Rectangle(double largeur, double hauteur) {
this.largeur = largeur;
this.hauteur = hauteur;
}
}
}
Cette conception bloque les chemins d'héritage par des constructeurs privés, n'autorisant que les sous-classes explicitement définies en interne. Chaque sous-classe est final, garantissant la sémantique d'immuabilité et l'intégrité structurelle, approchant l'effet des classes record.
5.2 Amélioration de l'expressivité de l'utilisation des classes scellées par la correspondance de modèles
Les classes scellées (sealed class) servent à limiter la structure d'héritage des classes. Leur combinaison avec la correspondance de modèles peut considérablement améliorer la lisibilité et la sécurité du code. En énumérant précisément tous les sous-types, le compilateur peut vérifier l'exhaustivité du couverture des branches.
Exemple de collaboration entre classes scellées et correspondance de modèles
sealed class Resultat
data class Succes(val donnees: String) : Resultat()
data class Erreur(val message: String) : Resultat()
traiterResultat(resultat: Resultat) = when (resultat) {
is Succes -> println("Succès: ${resultat.donnees}")
is Erreur -> println("Échec: ${resultat.message}")
}
Dans ce code, Resultat est une classe scellée n'autorisant que des sous-classes définies dans le même fichier. L'expression when agit comme un outil de correspondance de modèles, identifiant automatiquement le type spécifique. En raison de la structure d'héritage fermée des classes scellées, le compilateur peut s'assurer que when couvre toutes les possibilités, évitant les omissions de traitement.
Analyse des avantages
- Sécurité des types : vérification à la compilation de l'exhaustivité des branches, prévention des erreurs à l'exécution
- Sémantique claire : le code reflète directement les branches logiques métier
- Facilité de maintenance : lors de l'ajout de sous-classes, le compilateur suggère de compléter les branches de correspondance
5.3 Répartition appropriée des responsabilités entre classes scellées et classes record dans les modèles de domaine
Dans la conception pilotée par le domaine (DDD), les classes scellées (sealed class) et les classes record (record class) assument des responsabilités sémantiques différentes. Les classes scellées sont adaptées pour définir des structures d'héritage limitées, souvent utilisées pour exprimer des transitions d'état finies.
Modélisation des limites d'état avec des classes scellées
public sealed interface ResultatPaiement
autorise PaiementReussi, PaiementRejete, PaiementEnAttente {}
Ce code définit un système de types fermé pour les résultats de paiement, garantissant que tous les sous-types sont déclarés explicitement, améliorant la sécurité et la maintenabilité de la correspondance de modèles.
Classes record pour porter des données immuables
Les classes record sont adaptées pour encapsuler des objets de valeur, supportant naturellement l'immuabilité et la判断 d'égalité structurelle.
public record Adresse(rue: String, ville: String) {}
Une instance d'Adresse, une fois créée, n'est plus modifiable. Ses méthodes equals, hashCode et toString sont générées automatiquement par le compilateur, réduisant le code样板.
| Caractéristique | Classe scellée | Classe record |
|---|---|---|
| Usage principal | Contrôler la hiérarchie d'héritage | Encapsuler des porteurs de données |
| Mutabilité | Mutable ou immuable | Immuable par défaut |
5.4 Pratique : Refactorisation de la conception d'une classe scellée pour les statuts de commande d'e-commerce
Dans les systèmes e-commerce, les statuts de commande ont des chemins de transition d'état clairs et limités. L'utilisation de classes scellées (Sealed Class) peut efficacement contraindre la légalité des états, empêchant les transitions d'état invalides.
Définition des statuts de commande avec des classes scellées
sealed class StatutCommande {
object Cree : StatutCommande()
object Payee : StatutCommande()
object Expediee : StatutCommande()
object Terminee : StatutCommande()
object Annulee : StatutCommande()
}
Ce code limite toutes les sous-classes d'une classe scellée à être définies dans le même fichier, garantissant la fermeture des états. Chaque objet représente un état immuable, évitant l'instanciation illégale.
Validation des transitions d'état
| État actuel | États suivants autorisés |
|---|---|
| Cree | Payee, Annulee |
| Payee | Expediee, Annulee |
| Expediee | Terminee |
En combinant les expressions when, on peut réaliser une vérification exhaustive. Le compilateur s'assure que tous les états sont traités, améliorant la robustesse du code.