• IT-Karriere:
  • Services:

Java 15: Sealed Classes - Code-Smell oder moderne Erweiterung?

Was bringt das Preview Feature aus Java 15, wie wird es benutzt und bricht das nicht das Prinzip der Kapselung?

Eine Analyse von Boris Mayer veröffentlicht am
"Code-Smell" steht für funktionierenden, jedoch schlecht struktuierten Quellcode.
"Code-Smell" steht für funktionierenden, jedoch schlecht struktuierten Quellcode. (Bild: Pixabay)

Die als Vorschau in Java 15 eingeführten Sealed Classes und Interfaces halten sich nicht an den in der Objektorientierung etablierten Grundsatz, die Implementierungen eines abstrakten Typs zu verbergen. Stattdessen wird bei den Sealed Classes und Interfaces gleich in der Deklaration festgelegt, welche Klassen implementieren oder erweitern dürfen. Die Frage "Code-Smell oder geniale Erweiterung?" darf natürlich gestellt werden, zur Beantwortung braucht es aber auch Wissen darüber, wie es funktioniert und wozu es da ist.

Preview Features sind zwar benutzbar, müssen aber aktiviert werden

Stellenmarkt
  1. Stromnetz Hamburg GmbH, Hamburg
  2. Fresenius Medical Care Deutschland GmbH, St. Wendel

Bei den Sealed Classes handelt es sich in der vorliegenden Version der Programmiersprache um ein Preview Feature. Preview Features gibt es seit Java 12 und sie sind dazu da, neue Konzepte, Implementierungen oder APIs einer breiten Entwicklergemeinschaft vorzustellen, in der Hoffnung, Feedback zu erhalten. Das kann dann gegebenenfalls noch zu Änderungen in den folgenden Versionen führen - bei Preview Features ist man noch nicht festgelegt, die Regeln zum Vermeiden von codebrechenden Änderungen gibt es noch nicht.

Standardmäßig sind die Preview Features in den JDK-Versionen deshalb auch deaktiviert. Wer sie ausprobieren will, muss sie aktivieren, am besten in den entsprechenden Maven- oder Gradle-Projektdateien mit --enable-preview als compilerArgs. In den IDEs IntelliJ IDEA oder Eclipse lässt sich das auch einstellen, ist aber entsprechend schnell bei einem erneuten Importieren wieder weg - gerade bei IDEA passiert dies häufig.

Die Preview Features wurden eingeführt, um Designfehler zu verhindern. Denn das Kompatibilitätsversprechen macht nachträgliche, codebrechende Änderungen an der API schwer. Und wenn man nicht genau aufpasst, werden schöne neue Änderungen in den folgenden Jahren schnell mal zu Ballast, der eine elegantere oder mächtigere Lösung verhindert. Mit den Preview Features hofft man, möglichst viele interessierte Augen auf eine Lösung zu bekommen, bevor sie fest im Standard landet.

Die ersten Schritte: Sealed deklarieren

Der Sinn von Sealed Classes ist es, Kontrolle darüber zu haben, welche Subtypen erlaubt sind. Sinnvoll ist das dann, wenn eine endliche Liste von Subtypen abgebildet werden soll, also von Anfang an bekannt ist, welche Subtypen es gibt. Das erinnert ein wenig an Enums; auch hier wird eine festgelegte Liste von möglichen Werten, die ein Enum repräsentieren kann, erzeugt.

Im Gegensatz zu einem Enum, bei dem jeder Wert genau eine Instanz für jeden Eintrag vorhält, gibt es bei den Sealed Classes aber die Möglichkeit, mehrere Instanzen der einzelnen Subtypen zu bilden - sie können also individuelle Daten enthalten und stellen nicht nur, wie die Enums, einen Einzelwert an sich dar.

Deklariert werden Sealed Classes mit dem Keyword Sealed, zusätzlich benötigen sie dann noch eine kommaseparierte Liste der zugelassenen Subtypen.

  1. sealed interface Tier
  2. permits Hund, Katze, Maus {
  3. ...
  4. }

Hund, Katze und Maus können nun das Interface Tier implementieren, allerdings gibt es dafür ein paar Regeln: Die Implementierungen müssen im gleichen Modul liegen - im unbenannten Modul sogar im gleichen Package - und sie müssen sich explizit ihre Erweiterbarkeit deklarieren, solange dies nicht implizit festgelegt ist, wie bei Reccords oder Enums. Die explizite Festlegung kann dabei ein erneutes Sealed sein, es kann final sein oder eben die Klasse für Vererbung wieder öffnen, um die freie Weitervererbung zu erlauben.

  1. public final class Katze implements Tier {
  2. ...
  3. }

  1. public non-sealed class Hund implements Tier {
  2. ...
  3. }

  1. public sealed class Maus implements Tier
  2. permits WildMaus, HausMaus {
  3. ...
  4. }

Befinden sich die Sealed Class oder das Interface in der gleichen Compilation-Unit wie die Subtypen, kann die Implements Clause weggelassen werden. Im Beispiel hier wegen der einfacheren Darstellung als Records, die ja implizit final sind.

  1. sealed interface Tier {
  2. record Hund (String name, String rasse, boolean istStubenrein) implements Tier {}
  3. record Katze (String name) implements Tier {}
  4. record Maus (MausTypEnum typ) implements Tier {}
  5. ...
  6. }

Hier wird dann angenommen, dass die Liste der Tiere durch die Deklaration erschöpfend ist. Im weiteren Text wird allerdings davon ausgegangen, dass die Klassenimplementierung mit eigenen Methoden gewählt wurde.

Die Anwendung

Praktische Anwendung findet das Ganze zurzeit zum Beispiel im patterns matchenden instanceof.

  1. if (tier instanceof Hund h) {
  2. System.out.println("Der Hund der Rasse "
  3. + h.getRasse()
  4. + " heißt "
  5. + h.getName()
  6. + " und ist "
  7. + (x?"":"nicht ")
  8. + "stubenrein");
  9. }

Eine erste triviale Verwendung, aber das liegt daran, dass die wertvollere Implementierung für die Verwendung in Switch noch nicht fertig ist und in einer der nächsten Versionen von Java nachgeliefert werden soll. Und dort gilt dann wie beim Enum, dass eine Default-Clause nicht benötigt wird. Der Compiler kann dann feststellen, ob da auch alle möglichen Typen abgefragt wurden. In der Folge erleichtert dies die Pflege der Software ungemein: Der Compiler findet heraus, wo noch etwas zusätzlich abzufragen ist, wenn in der Zukunft die Liste der möglichen Tiertypen doch noch einmal um ein Meerschweinchen erweitert werden soll.

Und was ändert das in der realen Welt?

Am besten lässt sich das an Beispielen in der jdk beschreiben, bei denen das Kind bereits in den Brunnen gefallen ist: API-Stellen, bei denen die Sealed Classes echt praktisch gewesen wären und einfach zu spät kommen.

Besonders gilt das für die Concurrency API. Fragt man ein Future mit get() ab, so bekommt der Aufruf entweder das Ergebnis zurück, Timeout, Unterbrechung oder ein Fehlschlagen werden mit einer Exception beantwortet. Hier könnte mit den neuen Mitteln Sealed Classes und Records einfach eine Behandlung implementiert werden, die ohne über das Future transportierte Exceptions abläuft: Schließlich ist die hier geworfene Exception nicht bei der Ausführung von get() passiert, sondern sie ist während der Abarbeitung des gestarteten Threads geschehen.

Die Liste der Rückgabetypen ist endlich, damit kann dann auch die Auswertung entsprechend einfach geregelt werden und da die einzelnen Record-Implementierungen unterschiedliche Daten beinhalten können, geht auch keine Information verloren.

Sind Sealed Classes eine positive Entwicklung?

Sealed Classes haben mehrere sinnvolle Verwendungszwecke. Als Domänenmodellierungstechnik sind sie im Domänenmodell nützlich, wenn es darum geht, eine feststehende Liste von Alternativen zu erfassen. Dazu ermöglichen sie es, die Zugriffs- und die Erweiterungsfähigkeit zu trennen. Eine Final Class kann ein Interface implementieren und darüber angesprochen werden, ohne dass es eine alternative Implementierung der Final Class geben kann.

Bisher konnte man nur deklarieren, ob eine Klasse erweiterbar sein soll oder nicht. Sollte eine Klasse nicht wild erweitert werden dürfen, behalf man sich damit, die Klasse mit einem Package-private-Konstruktor zu versehen und alle Erben der Klasse in dasselbe Package zu legen. Zweckmäßig vielleicht, aber designtechnisch sicher keine elegante Lösung, vor allem, weil zu diesem Zweck oft Interfaces zu Abstract Classes erhoben werden.

Sealed Classes - sowohl Klassen als auch Interfaces, denn beide werden von java.lang.Class repräsentiert - beinhalten auf jeden Fall eine sehr enge Kopplung. Das Wissen, von welchen Erben eine Klasse oder ein Interface erweitert oder implementiert wird, ist Wissen über Implementierungsdetails, die sie nach traditioneller Sicht nicht haben sollten. Aber die Einführung von Sealed Classes heißt ja nicht, dass das plötzlich für alle Klassen der Fall sein muss. Eine enge Bindung kann sinnvoll sein und es kann viel Overhead ersparen. Und an Stellen, an denen es wirklich gebraucht wird, hilft die Deklaration einer Klasse als Sealed Krücken zu ersparen, die in sich ebenfalls eine Abkehr vom sauberen, objektorientierten Programmieren darstellen.

Java ist auch nicht die erste Programmiersprache, die das Konzept der Sealed Classes einführt: Scala hat sie schon. Gemeinsam mit den Records geben sie eine sinnvolle Öffnung ab - auch im Hinblick auf Projekt Valhala, zu dem diese beiden Änderungen gehören.

Bitte aktivieren Sie Javascript.
Oder nutzen Sie das Golem-pur-Angebot
und lesen Golem.de
  • ohne Werbung
  • mit ausgeschaltetem Javascript
  • mit RSS-Volltext-Feed


Anzeige
Mobile-Angebote
  1. 689€ (mit Rabattcode "PRIMA10" - Bestpreis!)
  2. 328€ (mit Rabattcode "YDENUEDR6CZQWFQM" - Bestpreis!)
  3. 749€ (mit Rabattcode "PERFECTEBAY10" - Bestpreis!)
  4. 274,49€ (mit Rabattcode "PFIFFIGER" - Bestpreis!)

DieterMieter 27. Sep 2020 / Themenstart

Seit 10 Jahren entwickle ich (hauptsächlich .NET), und der Begriff ist mir kein einziges...

jankapunkt 23. Sep 2020 / Themenstart

Wundere mich auch warum das noch nicht verschoben wurde, Sprachen X > Sprache Y ist doch...

Das... 19. Sep 2020 / Themenstart

Siehe Artikel, der Vorteil liegt darin, dass Library entwickler ein zusätzliches...

Das... 19. Sep 2020 / Themenstart

Das Beispiel verstehe ich auch nicht ganz, denn dazu braucht es kein sealed *shrug*. Ein...

Lord Gamma 19. Sep 2020 / Themenstart

An Modulgrenzen lässt man Objektorientierung weiterhin gerne außen vor und kümmert sich...

Kommentieren


Folgen Sie uns
       


Yakuza - Like a Dragon - Gameplay (Xbox Series X)

Im Video zeigt Golem.de, wie Yakuza - Like a Dragon auf der Xbox Series X aussieht.

Yakuza - Like a Dragon - Gameplay (Xbox Series X) Video aufrufen
5G: Nokias und Ericssons enge Bindungen zu Chinas Führung
5G
Nokias und Ericssons enge Bindungen zu Chinas Führung

Nokia und Ericsson betreiben viel Forschung und Entwicklung zu 5G in China. Ein enger Partner Ericssons liefert an das chinesische Militär.
Eine Recherche von Achim Sawall

  1. Quartalsbericht Ericsson mit Topergebnis durch 5G in China
  2. Cradlepoint Ericsson gibt 1,1 Milliarden Dollar für Routerhersteller aus
  3. Neben Huawei Telekom wählt Ericsson als zweiten 5G-Ausrüster

Vivo X51 im Test: Vivos gelungener Deutschland-Start hat eine Gimbal-Kamera
Vivo X51 im Test
Vivos gelungener Deutschland-Start hat eine Gimbal-Kamera

Das Vivo X51 hat eine gute Kamera mit starker Bildstabilisierung und eine vorbildlich zurückhaltende Android-Oberfläche. Der Startpreis in Deutschland könnte aber eine Herausforderung für den Hersteller sein.
Ein Test von Tobias Költzsch

  1. Software-Entwicklung Google veröffentlicht Android Studio 4.1
  2. Jetpack Compose Android bekommt neues UI-Framework
  3. Google Android bekommt lokale Sharing-Funktion

CalyxOS im Test: Ein komfortables Android mit einer Extraportion Privacy
CalyxOS im Test
Ein komfortables Android mit einer Extraportion Privacy

Ein mobiles System, das sich für Einsteiger und Profis gleichermaßen eignet und zudem Privatsphäre und Komfort verbindet? Ja, das geht - und zwar mit CalyxOS.
Ein Test von Moritz Tremmel

  1. Alternatives Android im Test /e/ will Google ersetzen

    •  /