• 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. Allianz Deutschland AG, Stuttgart
  2. Versicherungskammer Bayern, München

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
Hardware-Angebote
  1. (reduzierte Überstände, Restposten & Co.)

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
       


Mobilfunk: UMTS-Versteigerungstaktik wird mit Nobelpreis ausgezeichnet
Mobilfunk
UMTS-Versteigerungstaktik wird mit Nobelpreis ausgezeichnet

Sie haben Deutschland zum Mobilfunk-Entwicklungsland gemacht und wurden heute mit dem Nobelpreis ausgezeichnet: die Auktionstheorien von Paul R. Milgrom und Robert B. Wilson.
Ein IMHO von Frank Wunderlich-Pfeiffer

  1. Coronakrise Deutsche Urlaubsregionen verzeichnen starke Mobilfunknutzung
  2. LTE Telekom benennt weitere Gewinner von "Wir jagen Funklöcher"
  3. Mobilfunk Rufnummernportierung darf maximal 7 Euro kosten

Differential Privacy: Es bleibt undurchsichtig
Differential Privacy
Es bleibt undurchsichtig

Mit Differential Privacy soll die Privatsphäre von Menschen geschützt werden, obwohl jede Menge persönlicher Daten verarbeitet werden. Häufig sagen Unternehmen aber nicht, wie genau sie das machen.
Von Anna Biselli

  1. Strafverfolgung Google rückt IP-Adressen von Suchanfragen heraus
  2. Datenschutz Millionenbußgeld gegen H&M wegen Ausspähung in Callcenter
  3. Personenkennziffer Bundestagsgutachten zweifelt an Verfassungsmäßigkeit

Apple: iPhone 12 bekommt Magnetrücken und kleinen Bruder
Apple
iPhone 12 bekommt Magnetrücken und kleinen Bruder

Das iPhone 12 ist mit einem 6,1-Zoll- und das iPhone 12 Mini mit einem 5,4-Zoll-Display ausgerüstet. Ladegerät und Kopfhörer fallen aus Gründen des Umweltschutzes weg.

  1. Apple iPhone 12 Pro und iPhone 12 Pro Max werden größer
  2. Apple iPhone 12 verspätet sich
  3. Back Tap iOS 14 erkennt Trommeln auf der iPhone-Rückseite

    •  /