Classes scellées (Java 17+)
Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 52 sur 87.
En Java traditionnel, lorsque vous créez une classe, n'importe quelle autre classe peut l'étendre (à moins que vous ne la marquiez comme final). Mais que se passe-t-il si vous voulez autoriser l'héritage, mais pas de la part de n'importe qui ? Java 17 a introduit les classes scellées (sealed classes) pour vous donner un contrôle précis sur les classes qui peuvent étendre la vôtre.
Une classe scellée déclare explicitement ses sous-classes autorisées à l'aide des mots-clés sealed et permits :
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}
final class Circle extends Shape {
double radius;
double area() { return Math.PI * radius * radius; }
}
final class Rectangle extends Shape {
double width, height;
double area() { return width * height; }
}
final class Triangle extends Shape {
double base, height;
double area() { return 0.5 * base * height; }
}Seuls Circle, Rectangle et Triangle peuvent étendre Shape. Toute autre classe tentant de l'étendre provoquera une erreur de compilation.
Chaque sous-classe autorisée doit utiliser l'un des trois modificateurs : final (pas d'extension supplémentaire), sealed (poursuit la chaîne de restriction) ou non-sealed (ouvre à une extension sans restriction) :
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Ne peut pas être étendue
non-sealed class Truck extends Vehicle { } // N'importe qui peut étendre TruckLes classes scellées sont particulièrement puissantes avec le filtrage par motif (pattern matching) dans les expressions switch, car le compilateur connaît tous les sous-types possibles et peut vérifier l'exhaustivité. Elles sont idéales pour modéliser un ensemble fixe de types dans un domaine, comme les méthodes de paiement, les types de réponse ou les formes géométriques.
Défi
FacileConstruisons un système de notification qui démontre l'utilisation des classes scellées (sealed classes) en restreignant les types de notifications pouvant exister dans votre application. Vous allez créer une hiérarchie où seuls des types de notification spécifiques sont autorisés, et chaque type gère la distribution différemment.
Vous organiserez votre code sur quatre fichiers :
Notification.java: Créez une classe abstraite scellée (sealed abstract class) qui sert de base à toutes les notifications. Elle doit autoriser exactement trois sous-classes :EmailNotification,SMSNotification, etPushNotification. Votre classe scellée a besoin d'un champ protégémessage(String), d'un constructeur pour l'initialiser, d'un getter pour le message, et d'une méthode abstraitedeliver()qui retourne une String décrivant comment la notification est envoyée.EmailNotification.java: Créez une classefinalqui étend Notification. Une EmailNotification possède un champ privé supplémentairerecipient(String) pour l'adresse e-mail. Son constructeur doit prendre à la fois le message et le destinataire, en appelant le constructeur parent avecsuper(). La méthodedeliver()doit retourner :"Sending email to " + recipient + ": " + message;SMSNotification.java: Créez une classenon-sealedqui étend Notification—cela permet à d'autres classes de l'étendre si nécessaire à l'avenir. Une SMSNotification possède un champ privé supplémentairephoneNumber(String). Son constructeur prend le message et le numéro de téléphone. La méthodedeliver()doit retourner :"Sending SMS to " + phoneNumber + ": " + message;PushNotification.java: Créez une classefinalqui étend Notification. Une PushNotification possède un champ privé supplémentairedeviceId(String). Son constructeur prend le message et l'identifiant de l'appareil. La méthodedeliver()doit retourner :"Sending push to device " + deviceId + ": " + message;Main.java: Rassemblez votre système de notification ! Vous recevrez quatre entrées : un message (String), une adresse e-mail (String), un numéro de téléphone (String), et un identifiant d'appareil (String).Créez une instance de chaque type de notification en utilisant le même message mais leurs informations de contact respectives. Stockez les trois dans un tableau de type
Notification[]pour démontrer le polymorphisme avec les classes scellées. Ensuite, parcourez le tableau et affichez le résultat de l'appel àdeliver()sur chaque notification.
Vous recevrez quatre entrées dans l'ordre : message, adresse e-mail, numéro de téléphone, et identifiant d'appareil.
Remarquez comment la classe scellée restreint la hiérarchie—seules les trois classes autorisées peuvent étendre Notification. Chaque sous-classe doit se déclarer comme final, sealed, ou non-sealed. Cela vous donne un contrôle total sur votre hiérarchie de types tout en permettant le polymorphisme !
Aide-mémoire
Une classe scellée (sealed class) restreint les classes qui peuvent l'étendre en utilisant les mots-clés sealed et permits :
sealed class Shape permits Circle, Rectangle, Triangle {
abstract double area();
}Seules les classes listées dans permits peuvent étendre la classe scellée. Toute autre classe tentant de l'étendre provoquera une erreur de compilation.
Chaque sous-classe autorisée doit utiliser l'un des trois modificateurs suivants :
final- empêche toute extension ultérieuresealed- continue la chaîne de restriction avec ses propres sous-classes autoriséesnon-sealed- ouvre la hiérarchie pour une extension sans restriction
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Ne peut pas être étendue
non-sealed class Truck extends Vehicle { } // N'importe qui peut étendre TruckLes classes scellées sont utiles pour modéliser un ensemble fixe de types dans un domaine, tels que les méthodes de paiement, les types de réponse ou les formes géométriques. Elles fonctionnent bien avec le filtrage par motif (pattern matching) dans les expressions switch, car le compilateur connaît tous les sous-types possibles.
Essayez vous-même
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lire les quatre entrées
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: Créer un exemplaire de chaque type de notification en utilisant le même message
// - EmailNotification avec message et email
// - SMSNotification avec message et phoneNumber
// - PushNotification avec message et deviceId
// TODO: Stocker les trois notifications dans un tableau Notification[]
// Cela démontre le polymorphisme avec les sealed classes
// TODO: Parcourir le tableau et afficher le résultat de deliver()
// pour chaque notification
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé thisMéthodesChamps (Attributs)Méthode constructeurSurcharge de constructeurRécapitulatif - Calculatrice simple4Héritage
Héritage de base (extends)Le mot-clé superRedéfinition de méthode (@Override)Chaînage de constructeursLa classe ObjectHéritage simple et multiniveauPourquoi pas d'héritage multiple de classesRécapitulatif - Hiérarchie des employés7Méthodes spéciales et classe Object
Méthode toString()equals() et hashCode()Méthode clone()compareTo() et ComparableInterface ComparatorRécapitulatif - Tri personnalisé2Modificateurs d'accès et Encapsulation
Aperçu des niveaux d'accèsMéthodes Getter et SetterMasquage d'informationsLe mot-clé finalRécapitulatif - Gestionnaire de compte bancaire5Polymorphisme
Bases de la surcharge de méthodesRedéfinition de méthodes (Run-Time)Upcasting et DowncastingL'opérateur instanceofClasses et méthodes abstraitesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageAgrégation vs CompositionClasses internes, imbriquées et anonymesEnums et méthodes d'EnumRecords (Java 16+)Classes scellées (Java 17+)11Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron MonteurPatron ObservateurPatron Stratégie3Propriétés de classe et membres statiques
Variables d'instance vs variables statiquesMéthodes statiquesBlocs statiquesConstantes (static final)Récapitulatif - Compteur et utilitaire6Interfaces et Classes Abstraites
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesDefault et Static dans les InterfacesClasses Abstraites vs InterfacesInterfaces FonctionnellesRécapitulatif - Système de Paiement9La généricité
Introduction à la généricitéClasses génériquesMéthodes génériquesParamètres de type bornésWildcards (?, extends, super)Récapitulatif - Conteneur générique12Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositePatron Itérateur