Sealed Classes und Interfaces

In Java gab es schon immer (abstrakte) Klassen und Interfaces. Mit dem Release von Java 15 wurden Sealed Classes und Interfaces (versiegelte Klassen und Schnittstellen) eingeführt. Diese stellen eingeschränkte Klassenhierarchien dar, die mehr Kontrolle über die Vererbung bieten. Sealing ermöglicht es Classes und Interfaces, ihre erlaubten Subtypen zu definieren bzw. festzulegen, welche Klassen sie implementieren oder erweitern können.

Seit Java 15 gibt es neu Sealed Classes und Interfaces. Ziel der Neuerung ist es, die mögliche Vererbungshierarchie einzuschränken. Man möchte für eine Klasse bzw. Interface (nennen wir das Root) genau definieren, welche Klassen Root erweitern bzw. implementieren dürfen.

Sealed Classes und Interfaces im Onlineshop

Mit Hilfe eines Onlineshops soll das Konzept der Sealed Classes und Interfaces etwas verdeutlicht werden. Als Inspiration dazu dient eine Online Session, in der Heinz Kabutz live codiert hat.

  • Der Onlineshop hat Items, die einen Namen und einen Preis haben.
  • Die Kund*innen (Customer) haben ebenfalls einen Namen und eine PriceStrategy. Mit Hilfe dieser PriceStrategy könne wir normale Preise und Rabatte modellieren.
  • Die PriceStrategy ist als Interface modelliert, könnte aber auch eine (abstrakte) Klasse sein. Davon gibt es zwei konkrete Implementierungen:
    • die NormalPriceStrategy ohne Rabatt und
    • die LoyalCustomerPriceStrategy für regelmässige Kund*innen mit 10% Rabatt
  • Die Shopping Cart modellieren wir einfach als List <Item>.
  • Für die Preisberechnung gibt es einen PriceCalculator, der über die Liste der Items iteriert und die Preise der Items summiert, wobei die PriceStrategy des Customers angewendet wird.
Mit dem Release von Java 15 wurden Sealed Classes und Interfaces (versiegelte Klassen und Schnittstellen) eingeführt. Diese stellen eingeschränkte Klassenhierarchien dar, die mehr Kontrolle über die Vererbung bieten.

Nun kann das Ganze einfach zusammengefügt werden:
• Im Shop gibt es drei Items: Schuhe, Shirt und Hose.
• Es gibt zwei Customers: Peter und Susi. Beide haben jeweils alle drei Items in der Shopping Cart.
• Peter bestellt zum ersten Mal und muss den vollen Betrag zahlen.
• Susi ist Stammkundin und bekommt 10 % Rabatt.

Schutz vor Hacker Boris

So weit so gut. Wenn da nicht Hacker Boris wäre. Er implementiert sich eine eigene HackerPriceStrategy und bezahlt so nichts für seinen Einkauf.

Genau dieses Szenario soll verhindert werden. Dazu gibt es in Java schon länger die Möglichkeit, zur Laufzeit zu prüfen, ob eine PriceStrategy erlaubt ist oder nicht (Reflection lässt grüssen).

Schön wäre es allerdings, wenn man diese Prüfung während der Kompilierungszeit machen könnte. Und hier kommen die Sealed Classes und Interfaces nun ins Spiel. Man kann eine Klasse oder Interface – nennen wir das wieder Root – als «sealed» (versiegelt) markieren. Mittels «permits» kann man angeben, welche Klassen/Interfaces Root erweitern dürfen.

• Falls die Sub-Klassen beziehungsweise Sub-Interfaces im selben File sind wie Root, dann ist kein «permits» nötig. Wichtig: bei «sealed» muss aber mindestens eine Sub-Klasse im File implementiert sein.

• Wenn die Sub-Klassen bzw. Sub-Interfaces jeweils in einem anderen File sind als Root, muss deren Namen nach dem «permits» angegeben werden. Wenn man nicht mit Java Modulen arbeitet (d.h. man hat ein Unnamed Module), müssen die Erweiterungen im selben Package sein wie Root. Wichtig: Bei «sealed» und «permits» muss mindestens eine Klasse bei den «permits» angegeben werden.

• Die erweiterten Klassen müssen entweder final sein oder als «non-sealed» markiert werden. Damit kann man das «sealed» für konkrete Sub-Klassen beziehungsweise Sub-Interfaces wieder aufheben. Aber dies ist eine andere Geschichte und soll ein anderes Mal erzählt werden.

Nur so am Rande: die Information über «sealed» und «permits» ist im Java Bytecode hinterlegt.

Für den Wechsel auf Sealed Classes machen wir in unserem Beispiel folgende Anpassungen:
• PriceStrategy wird «sealed» und bekommt ein «permits».
• In der «permits» Liste geben wir unsere beiden konkreten Klassen an: LoyalCustomerPriceStrategy und NormalPriceStrategy.
• Und die Klassen LoyalCustomerPriceStrategy und NormalPriceStrategy werden final.

Wenn Hacker Boris nun versucht, seine HackerPriceStrategy zu implementieren, bekommt er ein Compile Error:

class is not allowed to extend sealed class: PriceStrategy (as it is not listed in its permits clause)

Genau was wir wollten: Wir schränken die Vererbungshierarchie zur Kompilierungszeit ein.

Sealed Classes und Interfaces sind sicher etwas, was vor allem bei der Entwicklung von Libraries gebraucht werden wird. Es ist dennoch gut, ein Grundverständnis für dieses neue Java Feature zu haben.

 

Weiterführende Links:

Heinz Kabutz | https://www.javaspecialists.eu
Venkat Subramaniam | Cruising Along with Java

Kommentare sind geschlossen.