Accès rapide :
La vidéo
Qu'est-ce qu'une classe scellée ?
Définition d'une classe scellée
Définir une classe dérivée d'une classe scellée
Définir une sous-classe finale
Définir une sous-classe ouverte
Définir une sous-classe elle-même scellée
Vous pouvez sceller d'autres types de données
--enable-preview
au démarrage de la JVM pour accéder à ces fonctionnalités). Si vous utiliser une version antérieure de Java, les codes proposés ne pourront pas
fonctionner : je vous recommande alors de télécharger une version moderne de Java.
Cette vidéo vous montre comment définir des classes scellées en Java, disponible depuis le Java SE 17. Une classe scellée permet de restreindre les possibilités d'héritage. Le tuto montre aussi comment définir des interfaces scellées.
Dans les chapitres précédents, nous avons vu qu'il est possible d'étendre, par héritage, une classe existante pour enrichir son comportement. Mais dans certaines situations et pour des raisons diverses (performances...), on ne souhaite ne pas permettre n'importe quoi en termes d'héritage. Nous avons vu qu'il était possible d'interdire l'héritage d'une classe grâce au mot clé final et ce depuis la première version de Java.
Depuis le Java SE 17, il est maintenant possible de restreindre les possibilités d'héritage, sans complétement les interdire : pour ce faire, on peut définir des « classes scellées ». Une classe scellée indique quels sont les types de données autorisés à dériver de cette classe.
scealed
, permits
et non-sealed
. Ces mots clés sont dits contextuels, car si vous les utilisez
comme noms de variables ou noms de méthodes, cela fonctionne très bien : effectivement avant l'apparition des classes scellées, ces mots n'étaient
pas réservés et peut-être en aviez-vous déjà utilisé un. Si tel est le cas, votre ancien programme continuera à compiler et à s'exécuter.
Pour définir une classe scellée, on utilise le mot clé contextuel sealed
. On complète la définition via le mot clé contextuel
permits
pour indiquer quels sont les types autorisés à dériver de la classe scellée. Voici un exemple d'utilisation.
1 2 3 4 5 6 7 8 9 |
package fr.koor.samples; public sealed class Shape permits Circle, Square { private Point center; // Imaginez d'autres attributs et méthodes. } |
Dans cet exemple, on part du principe qu'on ne désire pas autoriser la présence d'autres classes de figures géométriques.
Seuls des cercles et des carrés seront autorisés. Voici comment définir les classes Circle
et Square
.
1 2 3 4 5 6 7 |
public final class Circle extends Shape { private double radius; // Imaginez d'autres attributs et méthodes. } |
Square
.
Si vous tentez, tout de même, d'hériter de la classe scellée, voici le message d'erreur qui sera produit par votre compilateur.
1 2 3 4 5 |
public final class Triangle extends Shape { // Imaginez des attributs et des méthodes. } |
On lance manuellement la compilation de la classe non-autorisée.
$> javac Triangle.java Shape.java:1: error: class is not allowed to extend sealed class: Shape (as it is not listed in its permits clause) public final class Triangle extends Shape { ^ 1 error
Lorsque vous allez tenter de dériver une classe d'une classe parente scellée, trois alternatives vous sont proposées.
1 2 3 4 5 6 7 |
public class Square extends Shape { private double length; // Imaginez d'autres attributs et méthodes. } |
Et voici le message d'erreur produit par le compilateur :
Shape.java:1: error: sealed, non-sealed or final modifiers expected public class Square extends Shape { ^ 1 error
C'est très exactement ce que nous avons fait dans les exemples précédents. Si vous scellez une classe, c'est pour limiter le nombre de classes qui
vont en dériver (directement ou indirectement). Le fait de finaliser la classe fille interdira de créer une éventuelle sous-classe
ColoredCircle
qui dériverait de la classe Circle
.
1 2 3 4 5 6 7 |
public final class Circle extends Shape { private double radius; // Imaginez d'autres attributs et méthodes. } |
En faisant ce choix, vous indiquez au compilateur que toutes les sous-classes de la classes ouverte seront acceptées.
On introduit ce type de classe via le mot clé contextuel non-sealed
.
1 2 3 4 5 6 7 |
public non-sealed class Circle extends Shape { private double radius; // Imaginez d'autres attributs et méthodes. } |
Vous pouvez maintenant envisager de définir la sous-classe ColoredCircle
.
1 2 3 4 5 6 7 |
public class ColoredCircle extends Circle { private Color color; // Imaginez d'autres attributs et méthodes. } |
Enfin, on peut aussi sceller la sous-classe : dans ce cas, elle devra à son tour lister les sous-classes autorisées à en dériver.
1 2 3 4 5 6 7 |
public sealed class Circle extends Shape permits ColoredCircle { private double radius; // Imaginez d'autres attributs et méthodes. } |
Dans ce cas, vous devrez de nouveau choisir une des trois stratégies pour la définition de la classe ColoredCircle
:
pour ma part, j'ai choisi la stratégie « classe finale ».
1 2 3 4 5 6 7 |
public final class ColoredCircle extends Circle { private Color color; // Imaginez d'autres attributs et méthodes. } |
Il est aussi possible de sceller des classes abstraites ou des interfaces. Voici un exemple vous montrant comment sceller une interface, cette interface étant ensuite implémentée par une classe de type « record » (pourquoi pas). Voici le code de l'interface scellée.
1 2 3 4 5 6 |
public sealed interface Shape permits Circle, Square { double area(); double perimeter(); } |
Voici maintenant la définition de notre premier record Circle
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public record Circle (Point center, double radius) implements Shape { @Override public double area() { return Math.PI * radius() * radius(); } @Override public double perimeter() { return 2 * Math.PI * radius(); } } |
Et voici le code de notre second type record.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
record Square (Point center, double length) implements Shape { @Override public double area() { return length() * length(); } @Override public double perimeter() { return 4 * length(); } } |
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :