magicmarcy.de | Sealed Classes? Was ist denn das schon wieder?

Sealed Classes? Was ist denn das schon wieder?

Java • 27. November 2025 • Lesezeit: 6 Minuten

Sealed Classes wurden mit Java 17 eingeführt und sind eines dieser Features, das man beim ersten Lesen vielleicht überfliegt, aber beim genaueren Hinsehen richtig spannend wird - vor allem, wenn man versteht, wofür sie gedacht sind. In diesem Beitrag möchte ich dir als Einsteiger zeigen, was es mit Sealed Classes auf sich hat, warum sie nützlich sind und wie du sie praktisch einsetzen kannst.

Sealed Classes erlauben dir, ganz genau festzulegen, welche Klassen oder Interfaces von einer bestimmten Klasse erben dürfen. Damit bekommst du mehr Kontrolle über deine Vererbungshierarchie, ohne gleich alles mit final oder Paketgrenzen zu „verbauen“. Es ist also eine Art „gezielte Freiheit“ in der Vererbung.

 

Warum braucht man Sealed Classes überhaupt?

Bisher war es so: Wenn du eine Klasse geschrieben hast, konnte jeder, der Zugriff auf deinen Code hatte, diese Klasse erweitern - es sei denn, du hast sie als final markiert. Beispiel:

public class Tier {
    public void macheGeraeusch() {
        System.out.println("Ein Tier macht ein Geraeusch");
    }
}

Jetzt kann jemand einfach erben:

public class Hund extends Tier {
    @Override
    public void macheGeraeusch() {
        System.out.println("Wuff!");
    }
}

Das ist ja grundsätzlich gewollt. Aber was, wenn du nur ganz bestimmte Tierarten erlauben willst, die von Tier erben dürfen - zum Beispiel Hund und Katze, aber niemand sonst? Genau hier kommen Sealed Classes ins Spiel.

 

So definiert man eine Sealed Class

Eine Sealed Class wird mit dem Schlüsselwort sealed deklariert. Danach gibst du mit dem Schlüsselwort permits an, welche Klassen erben dürfen:

public sealed class Tier permits Hund, Katze {
    public void macheGeraeusch() {
        System.out.println("Ein Tier macht ein Geraeusch");
    }
}

Jetzt darf wirklich nur Hund und Katze von Tier erben. Wenn du versuchst, eine andere Klasse abzuleiten, bekommst du einen Compilerfehler.

Aber: Die erlaubten Unterklassen müssen selbst klar angeben, wie es mit ihnen weitergeht. Sie können:

  • final sein – dann darf niemand mehr weiter erben
  • sealed sein – dann kann man die Hierarchie gezielt weiter einschränken
  • non-sealed sein – dann darf wieder jeder erben

Ein Beispiel mit allen drei Varianten:

public final class Hund extends Tier {
    @Override
    public void macheGeraeusch() {
        System.out.println("Wuff!");
    }
}

public sealed class Katze extends Tier permits SiamKatze {
    @Override
    public void macheGeraeusch() {
        System.out.println("Miau!");
    }
}

public non-sealed class SiamKatze extends Katze {
    @Override
    public void macheGeraeusch() {
        System.out.println("Miau, aber feiner!");
    }
}

Hund ist final - da ist Schluss. Katze ist selbst wieder sealed, erlaubt aber nur SiamKatze. Diese ist non-sealed, also wieder offen für alle.

 

Ein Beispiel in Aktion

Wenn wir das Ganze ausprobieren wollen:

public class Main {
    public static void main(String[] args) {
        Tier t1 = new Hund();
        Tier t2 = new Katze();
        Tier t3 = new SiamKatze();

        t1.macheGeraeusch();
        t2.macheGeraeusch();
        t3.macheGeraeusch();
    }
}

Die Ausgabe wäre:

Wuff!
Miau!
Miau, aber feiner!

Ganz normale Polymorphie also. Der Unterschied liegt in der Kontrolle, die du als Entwickler über die Hierarchie hast.

 

Warum ist das sinnvoll?

Sealed Classes helfen, dein Design stabil und sicher zu halten. Wenn du zum Beispiel eine API entwickelst, willst du vielleicht verhindern, dass externe Entwickler unkontrolliert neue Subklassen anlegen, die eventuell nicht zu deiner Logik passen. Mit Sealed Classes kannst du das explizit festlegen, statt dich auf Kommentare oder Dokumentation zu verlassen.

Ein weiterer Vorteil: Der Compiler kann durch die eingeschränkte Hierarchie besser prüfen, ob du in einem switch-Statement alle Fälle abgedeckt hast. Beispiel:

public static void beschreibeTier(Tier tier) {
    switch (tier) {
        case Hund h -> System.out.println("Das ist ein Hund.");
        case Katze k -> System.out.println("Das ist eine Katze.");
    }
}

Weil der Compiler weiß, dass nur Hund und Katze erlaubt sind, ist das switch vollständig. Du musst keinen default-Zweig angeben. Versuchst du später eine neue erlaubte Klasse hinzuzufügen, wirst du beim Kompilieren darauf hingewiesen, dass dein switch nicht mehr vollständig ist.

 

Ein praktisches Beispiel aus dem Alltag

Stell dir vor, du arbeitest an einem System zur Verwaltung von Benutzern und Rechten. Es gibt verschiedene Typen von Nutzern - Administratoren, Standardbenutzer und Gäste. Du möchtest verhindern, dass später jemand eine neue Benutzerart erfindet, die Sicherheitslücken öffnen könnte. So könnte das aussehen:

public sealed class Benutzer permits Admin, StandardBenutzer, Gast {
    public abstract String rolle();
}

public final class Admin extends Benutzer {
    @Override
    public String rolle() {
        return "Administrator";
    }
}

public final class StandardBenutzer extends Benutzer {
    @Override
    public String rolle() {
        return "Standardbenutzer";
    }
}

public final class Gast extends Benutzer {
    @Override
    public String rolle() {
        return "Gast";
    }
}

Und wenn du das testest:

public class Test {
    public static void main(String[] args) {
        Benutzer b1 = new Admin();
        Benutzer b2 = new StandardBenutzer();
        Benutzer b3 = new Gast();

        System.out.println(b1.rolle());
        System.out.println(b2.rolle());
        System.out.println(b3.rolle());
    }
}

Ausgabe:

Administrator
Standardbenutzer
Gast

So kannst du klar und sicher vorgeben, welche Benutzerarten es geben darf - niemand kann mehr eine neue Klasse wie SuperAdmin erfinden, ohne die erlaubte Liste in der Benutzer-Klasse anzupassen.

 

Fazit

Sealed Classes sind ein elegantes Werkzeug, um Vererbung kontrollierter und sicherer zu gestalten. Für Einsteiger ist es vor allem wichtig zu verstehen: Sie sind kein Ersatz für Interfaces oder final-Klassen, sondern eine Ergänzung. Sie helfen dir, Code-Strukturen klarer zu definieren und Missbrauch zu verhindern.

Gerade wenn du später APIs oder Frameworks entwickelst, wirst du merken, wie wertvoll es ist, wenn du sagen kannst: „Nur diese Klassen dürfen erben.“ - und der Compiler sorgt dafür, dass sich alle daran halten. So bleibt dein Code konsistent, wartbar und verständlich.

Ich teile mein Wissen hier kostenlos, um anderen Entwicklern den Einstieg zu erleichtern. Wenn du das unterstützen möchtest, schau gern auf Ko-Fi vorbei ☕️♥️

Es wurden noch keine Kommentare verfasst, sei der erste!
Über
Avatar

Hi, ich bin Marcel!
Als Fachinformatiker für Anwendungsentwicklung und IHK-geprüfter Ausbilder teile ich auf meinem Blog Grundlagen- und Fortgeschrittenen-Wissen für angehende Entwickler*innen und Interessierte, sowie weitere spannende Themen aus der IT.

Blog Aktivität

Sep
 
 
 
Oct
 
 
 
Nov
 
 
 
 
Mon
Wed
Fri