Mit der Veröffentlichung von Java 17 als Long-Term-Support-Version (LTS) hat sich die Sprache um eine Vielzahl moderner Features erweitert, darunter das Konzept der Sealed Classes. Dieses Feature wurde bereits in Java 15 als Preview eingeführt, in Java 16 verbessert und mit Java 17 finalisiert. Sealed Classes bieten eine neue Möglichkeit, die Vererbung und Implementierung von Klassen, Interfaces und abstrakten Klassen präzise zu steuern. Dadurch lassen sich APIs sicherer gestalten, der Code wird robuster und klarer strukturiert.

Motivation: Warum Sealed Classes?

Traditionell erlaubt Java eine offene Vererbungshierarchie. Sobald eine Klasse oder ein Interface als public und nicht final deklariert ist, kann es theoretisch von jedem beliebigen anderen Code erweitert oder implementiert werden – sofern keine Einschränkungen durch Package-Sichtbarkeit oder final bestehen. Das kann in manchen Situationen zu unerwünschten Erweiterungen oder zu einer schwer kontrollierbaren API führen.

Sealed Classes adressieren genau dieses Problem. Sie ermöglichen es, die erlaubten Subtypen einer Klasse oder eines Interfaces explizit festzulegen. Das erhöht nicht nur die Wartbarkeit und Sicherheit des Codes, sondern verbessert auch die Möglichkeit zur statischen Analyse und Optimierung durch Werkzeuge oder den Compiler selbst.


Grundlagen: Was ist eine Sealed Class?

Eine Sealed Class (versiegelte Klasse) ist eine Klasse oder ein Interface, das explizit angibt, welche Klassen oder Interfaces es erweitern oder implementieren dürfen. Diese erlaubten Subtypen müssen entweder im gleichen Modul (bei modularen Projekten) oder im gleichen Paket liegen und die Basisklasse direkt erweitern oder implementieren.

Syntax

public sealed class Shape
    permits Circle, Rectangle, Square {
    // ...
}
Code-Sprache: PHP (php)

In diesem Beispiel ist Shape eine versiegelte Klasse, und nur Circle, Rectangle und Square dürfen sie erweitern.


Anforderungen an Subklassen

Jeder erlaubte Subtyp muss eine von drei Entscheidungen treffen:

  1. final – Die Klasse kann nicht weitervererbt werden.
  2. sealed – Die Klasse selbst ist wieder versiegelt und kontrolliert weitere Subtypen.
  3. non-sealed – Die Klasse hebt die Einschränkung der Vererbung auf; jeder darf sie erweitern.

Beispiele

public final class Circle extends Shape {
    // keine weitere Vererbung möglich
}

public sealed class Rectangle extends Shape
    permits FilledRectangle, EmptyRectangle {
    // weitere kontrollierte Vererbung
}

public non-sealed class Square extends Shape {
    // offen für beliebige Vererbung
}
Code-Sprache: PHP (php)

Verwendung mit Interfaces

Auch Interfaces können versiegelt werden. Das ist besonders hilfreich bei algebraischen Datentypen oder Entwurfsmustern wie dem Visitor-Pattern.

public sealed interface Expr permits Literal, Addition, Multiplication {
    double eval();
}
Code-Sprache: PHP (php)

So kann der Compiler sicherstellen, dass nur die erlaubten Implementierungen bei Pattern Matching oder instanceof-Prüfungen vorkommen.


Vorteile von Sealed Classes

1. Verbesserte API-Kontrolle

Die Kontrolle darüber, welche Klassen eine Basisklasse erweitern dürfen, erlaubt es API-Designern, genau festzulegen, wie eine Klasse verwendet werden soll. Das verhindert Missbrauch oder unbeabsichtigte Verwendungen.

2. Erhöhte Sicherheit

Durch Einschränkung der Vererbung kann man verhindern, dass externe Klassen auf intern gedachte Vererbungen zugreifen. Dies erhöht die Typsicherheit und Integrität einer Bibliothek oder eines Frameworks.

3. Bessere Unterstützung für Pattern Matching

In Kombination mit dem neuen Pattern Matching (ebenfalls in Java 17 verbessert) ermöglichen Sealed Classes eine vollständige und exakte Analyse der Subtypen. Das verbessert die Lesbarkeit und Wartbarkeit komplexer switch-Statements.

4. Statische Erkennung von Unvollständigkeit

Der Compiler kann erkennen, wenn nicht alle erlaubten Subtypen in einem switch-Block behandelt werden. Das reduziert Fehlerquellen und vermeidet unbeabsichtigte Fallthroughs.


Einschränkungen und Regeln

Sealed Classes unterliegen bestimmten Regeln:

  • Alle permits-Subtypen müssen in derselben Compilation Unit, im selben Paket oder im selben Modul definiert sein.
  • Jeder Subtyp muss die sealed, non-sealed oder final-Deklaration explizit vornehmen.
  • Anonyme Klassen können keine versiegelten Klassen erweitern.
  • Es kann nur explizit eine überschaubare Menge an Subtypen geben – dynamische Erweiterung zur Laufzeit ist ausgeschlossen.

Praxisbeispiel: Modellierung eines einfachen Ausdrucksbaums

public sealed interface Expr permits Value, Add, Multiply {
    double eval();
}

public record Value(double val) implements Expr {
    public double eval() {
        return val;
    }
}

public record Add(Expr left, Expr right) implements Expr {
    public double eval() {
        return left.eval() + right.eval();
    }
}

public record Multiply(Expr left, Expr right) implements Expr {
    public double eval() {
        return left.eval() * right.eval();
    }
}
Code-Sprache: PHP (php)

In diesem Beispiel modellieren wir einfache mathematische Ausdrücke. Dank sealed kann der Compiler sicher sein, dass Expr nur von Value, Add und Multiply implementiert wird – was besonders bei der Auswertung hilfreich ist.


Integration mit Pattern Matching (ab Java 17)

public double eval(Expr expr) {
    return switch (expr) {
        case Value v -> v.val();
        case Add a -> eval(a.left()) + eval(a.right());
        case Multiply m -> eval(m.left()) * eval(m.right());
    };
}
Code-Sprache: PHP (php)

Weil alle möglichen Implementierungen bekannt sind, kann der Compiler den switch-Block prüfen und sicherstellen, dass kein Fall vergessen wurde – ohne default.


Fazit

Sealed Classes in Java 17 sind ein mächtiges Werkzeug für die Gestaltung klarer, wartbarer und sicherer APIs. Sie schließen eine Lücke zwischen völlig offenen Vererbungshierarchien und starren, final deklarierten Klassen. Durch die explizite Kontrolle über erlaubte Subtypen lassen sich viele Designprobleme eleganter lösen, insbesondere in Kombination mit Pattern Matching und Records.

Wer robuste Architekturen gestalten oder Domänenmodelle präzise abbilden will, sollte die Möglichkeiten von Sealed Classes unbedingt in Betracht ziehen. Gerade in komplexeren Softwareprojekten – etwa im Bereich der Compiler-Entwicklung, bei domänenspezifischen Sprachen oder bei API-Designs – entfalten sie ihr volles Potenzial.