Versiegelte Klassen (Java 17+)
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 52 von 87.
Im traditionellen Java kann jede andere Klasse eine von dir erstellte Klasse erweitern (es sei denn, du markierst sie als final). Aber was, wenn du Vererbung zulassen möchtest, nur nicht von jedem? Java 17 führte versiegelte Klassen ein, damit du genau steuern kannst, welche Klassen deine Klasse erweitern dürfen.
Eine versiegelte Klasse deklariert ihre erlaubten Unterklassen ausdrücklich mithilfe der Schlüsselwörter sealed und permits:
sealed abstract 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; }
}Nur Circle, Rectangle und Triangle können Shape erweitern. Jede andere Klasse, die versucht, es zu erweitern, führt zu einem Kompilierungsfehler.
Jede zulässige Unterklasse muss einen von drei Modifikatoren verwenden: final (keine weitere Erweiterung), sealed (setzt die Einschränkungskette fort) oder non-sealed (öffnet für uneingeschränkte Erweiterung):
sealed class Vehicle permits Car, Truck { }
final class Car extends Vehicle { } // Kann nicht erweitert werden
non-sealed class Truck extends Vehicle { } // Jeder kann Truck erweiternVersiegelte Klassen sind besonders leistungsfähig in Verbindung mit Pattern Matching in switch-Ausdrücken, da der Compiler alle möglichen Untertypen kennt und die Vollständigkeit überprüfen kann. Sie eignen sich ideal, wenn du eine feste Menge von Typen in einer Domäne modellierst, etwa Zahlungsmethoden, Antworttypen oder geometrische Formen.
Aufgabe
EinfachErstellen wir ein Benachrichtigungssystem, das versiegelte Klassen demonstriert, indem es einschränkt, welche Arten von Benachrichtigungen in deiner Anwendung existieren können. Du erstellst eine Hierarchie, in der nur bestimmte Benachrichtigungstypen zulässig sind und jeder Typ die Zustellung anders behandelt.
Du organisierst deinen Code auf vier Dateien verteilt:
Notification.java: Erstelle eine versiegelte abstrakte Klasse, die als Basisklasse für alle Benachrichtigungen dient. Sie soll genau drei Unterklassen erlauben:EmailNotification,SMSNotificationundPushNotification. Deine versiegelte Klasse benötigt ein geschütztes Feldmessage(String), einen Konstruktor zu dessen Initialisierung, einen Getter für die Nachricht sowie eine abstrakte Methodedeliver(), die einen String zurückgibt, der beschreibt, wie die Benachrichtigung gesendet wird.EmailNotification.java: Erstelle einefinal-Klasse, die Notification erweitert. Eine EmailNotification verfügt über ein zusätzliches privates Feldrecipient(String) für die E-Mail-Adresse. Ihr Konstruktor soll sowohl die Nachricht als auch den Empfänger entgegennehmen und den Konstruktor der übergeordneten Klasse mitsuper()aufrufen. Die Methodedeliver()soll Folgendes zurückgeben:Sending email to [recipient]: [message]SMSNotification.java: Erstelle einenon-sealed-Klasse, die Notification erweitert. Dadurch können andere Klassen sie bei Bedarf in Zukunft erweitern. Eine SMSNotification verfügt über ein zusätzliches privates FeldphoneNumber(String). Ihr Konstruktor nimmt die Nachricht und die Telefonnummer entgegen. Die Methodedeliver()soll Folgendes zurückgeben:Sending SMS to [phoneNumber]: [message]PushNotification.java: Erstelle einefinal-Klasse, die Notification erweitert. Eine PushNotification verfügt über ein zusätzliches privates FelddeviceId(String). Ihr Konstruktor nimmt die Nachricht und die Geräte-ID entgegen. Die Methodedeliver()soll Folgendes zurückgeben:Sending push to device [deviceId]: [message]Main.java: Führe dein Benachrichtigungssystem zusammen! Du erhältst vier Eingaben: eine Nachricht (String), eine E-Mail-Adresse (String), eine Telefonnummer (String) und eine Geräte-ID (String).Erstelle jeweils eine Benachrichtigung jedes Typs mit derselben Nachricht, aber den jeweiligen Kontaktinformationen. Speichere alle drei in einem Array vom Typ
Notification[], um Polymorphie mit versiegelten Klassen zu demonstrieren. Iteriere anschließend durch das Array und gib das Ergebnis des Aufrufs vondeliver()für jede Benachrichtigung aus.
Du erhältst vier Eingaben in dieser Reihenfolge: Nachricht, E-Mail-Adresse, Telefonnummer und Geräte-ID.
Beachte, wie die versiegelte Klasse die Hierarchie einschränkt: Nur die drei erlaubten Klassen können Notification erweitern. Jede Unterklasse muss sich als final, sealed oder non-sealed deklarieren. Dadurch erhältst du vollständige Kontrolle über deine Typhierarchie und kannst gleichzeitig Polymorphie ermöglichen!
Probier es selbst
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lies die vier Eingaben
String message = scanner.nextLine();
String email = scanner.nextLine();
String phoneNumber = scanner.nextLine();
String deviceId = scanner.nextLine();
// TODO: Erstelle je eine Benachrichtigung jedes Typs mit derselben Nachricht
// - EmailNotification mit message und email
// - SMSNotification mit message und phoneNumber
// - PushNotification mit message und deviceId
// TODO: Speichere alle drei Benachrichtigungen in einem Notification[]-Array
// Dies demonstriert Polymorphismus mit sealed classes
// TODO: Iteriere durch das Array und gib das Ergebnis von deliver() aus
// für jede Benachrichtigung
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – ZahlungssystemÜbe selbstständig: Online-Java-Compiler