Sealed Classes
wurden mit JEP-409 final als Feature in die Sprache Java aufgenommen und ermöglichen es die erlaubten Subtypen einer Klasse bzw. eines Interfaces schon bei der
Definition der Klasse (bzw. des Interfaces) anzugeben.
Mit Sealed Classes
ist es in Fällen in denen Klassen nicht für eine beliebige Ableitung auch von Drittcode entworfen wurde möglich das Fragile Base Class Problem zu umgehen.
Sealed Classes
eignen sich insbesondere im Zusammenhang mit record classes
dazu im Rahmen eines eher funktionalen Programmierstils für die Umsetzung Algebraischer Datentypen in Java.
Sealed Classes und Interfaces
Eine Sealed Class
, kann sowohl eine konkrete Klasse, eine abstrakte Klasse oder auch ein Interface sein.
Das hauptsächliche Ziel von Sealed Classes
ist es Möglichkeiten der Beschränkung der Vererbung, sowie die Verbesserung des Pattern-Matchings hinsichtlich “vollständiger” Switch-Statements zu unterstützen..
Durch die Deklaration einer Klasse bzw. eines Interface als sealed
erhält man explizite Kontrolle darüber, welche Klassen bzw. Interfaces dieses .
Als Beispiel sei hier ein Beispiel auf JEP-409 genannt:
public sealed interface Expr permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public final class ConstantExpr implements Expr { ... }
public final class PlusExpr implements Expr { ... }
public final class TimesExpr implements Expr { ... }
public final class NegExpr implements Expr { ... }
Dieses Beispiel definiert ein sealed
Interface, das die Schnittstelle eines Objektgraphen darstellt der matematische Ausdrücke mit den Operatoren für
Addition, Negation und Multiplikation sowie für konstante Ausdrücke beschreibt.
Mit dem Schlüsselwort sealed
wird die Deklaration einer Sealed Class, oder wie hier eines Sealed Interface eingeleitet. Die Kennzeichnung als sealed
führt dazu,
das das Interface nur von Klassen implementiert werden kann die explizit in der permits
Klausel genannt sind, d.h. hier ConstantExpr, PlusExpr, TimesExpr sowie NegExpr.
Die nach permits
genannten Interfaces oder Klassen müssen direkte Subtypen des Sealed-Interface sein und selbst für die Verwendung als Subtyp einer Sealed-Class oder eines Sealed-Interface geeignet sein.
Voraussetzung für die Verwendung einer Klasse bzw. eines Interface als Subtyp einer Sealed Class bzw. eines Sealed Interface
Um die letztgenannte Voraussetzung zu erfüllen und als Subtyp einer Sealed Classe/eines Sealed Interface geeignet zu sein muss eine Klasse bzw. ein Interface selbst entweder
als sealed
, als non-sealed
oder als final
gekennzeichnet sein.
Die Bedeuting dieser Schlüsselwörter ist im Einzelnen :
- sealed Eine als
sealed
gekennzeichnete Klasse (oder ein solches Interface) muss einepermits
Klausel habe in der die Klassen bzw. Interfaces genannt werden denen es erlaubt ist von dersealed
Class (bzw. dem Sealed Interface) zu erben. Die in derpermits
Klausel genannten Typen müssen direkte Subtypen dessealed
Typen sein. - non-sealed Markiert Subtypen eines
sealed
Typ die selbst offen für Erweiterungen durch Ableitung oder Implementierung sind. Inswitch
Expressions wird dann gegen diesen Typ und nicht etwa mögliche Subtypen geprüft um sicherzustellen, daß eine Vollständigkeit derswitch
-Expression gegeben ist. - final Hat seine Bedeutung mit der Einführung von Sealed-Classes/Interfaces nicht geändert und verhindert wie bisher, daß von der
final
deklarierten Klasse andere Klassen abgeleitet werden können. Wichtig Record Classes sind implizitfinal
, so daß bei Verwendung einesrecord
als Subtyp einessealed
Typen auf die Angabe vonfinal
verzichtet werden kann.
Hintergrund der Anforderung, daß alle direkten Subtypen eines sealed
Type entweder sealed
, non-sealed
oder final
sein müssen ist, daß es auf diese Weise dem Compiler ermöglicht wird für Switch-Expressions über dem sealed
Typ zu ermitteln, ob der switch
“vollständig” ist, und somit also alle möglichen Ausprägungen des Typ abdeckt. Beispielsweise könnte der Compiler für folgendes Beispiel feststellen, das ein Case-Zweig für TimesExpr fehlt.
Expr exp=getExpr();
switch (exp) {
case ConstantExpr ce -> { ... }
case PlusExpr pe -> { ... }
case NegExpr ne -> { ... }
}
Hier kann also der Compiler aufgrund der sealed
Eigenschaft des Expr Interface sicherstellen, daß der Zweig case TimesExpr te -> { ... }
ergänzt wird und der switch
vollständig ist.
Weiterhin ist es notwendig, daß alle Subtypen von sealed
Klassen oder Interfaces sich im gleichen Module befinden und falls es sich bei dem Module um das unnamed Module handelt sich Sealed-Typ und Subtypen im gleichen Package befinden.
Fazit
sealed types
im JDK 17 bieten einen Mechanismus zur Steuerung der Vererbung und Implementierung. Durch die explizite Angabe zulässiger Subtypen wird es möglich beim Mustervergleich von Case-Expressions die Vollständigkeit sicherzustellen und es werden Probleme mit nicht für die Vererbung vorgesehenen Basisklassen vermieden. Darüber hinaus unterstützen sealed types
die Umsetzung Algebraischer Datentypen und damit einen eher funktionalen Programmierstil.