Die Preview-Features von Java 22

19.03.2024

In meinem gestern erschienenen Blogpost habe ich die neuen Features von Java 22 vorgestellt. Doch mindestens genaus spannend – und zudem zahlreicher – sind die in der Version enthaltenen Preview-Features. Diese möchte ich im Folgenden jeweils kurz vorstellen.

JEP-461: Stream Gatherers (Preview)

Die Menge der in der Stream-API vorhandenen Zwischenoperationen (wie z.B. filter, map, sorted, …) ist derzeit fix und nicht erweiterbar.

Seit der Einführung von Streams in Java 8, was nunmehr ganze 10 Jahre her ist, wurden viele Vorschläge für weitere Zwischenoperationen gemacht, die jede für sich zwar sinnvoll wäre, aber alle zusammen genommen die ohnehin schon große API zu unübersichtlich machen würden.

Stattdessen wird die Stream-API mittels eines Extension Points namens „Stream Gatherers“ erweiterbar gemacht. Zu diesem Zweck erhält die API die neue Zwischenoperation „gather“, der man eigene, oder von der Java-API bereitgestellte, Implementierungen des „Gatherer“-Interface mitgeben kann.

Der folgende Code zeigt die Factory-Methode für einen einfachen Gatherer, der jedes Element im Stream vervielfacht:

Die Verwendung im Stream sieht wie folgt aus:

Das Ergebnis ist eine Liste, die jedes Element aus dem ursprünglichen Stream dreimal enthält (a, a, a, b, b, b, c, c, c).

Eine Reihe von Gatherer-Implementierungen, wie beispielsweise windowFixed, windowSliding oder fold, sind über die Klasse Gatherers erhältlich.

Die Anwendung von windowSliding sieht etwa wie folgt aus:

Die Ausgabe dazu:

JEP-447: Statements before super(…) (Preview)

Bisher müssen Aufrufe von super(…) bzw. this(…) in Konstruktoren immer an erster Stelle stehen. Mit diesem JEP wird es möglich, bestimmte Statements diesen Aufrufen voranzustellen. Für solche Statements gibt es allerdings Einschränkungen, sie dürfen etwa keine Referenzen auf die zu erstellende Instanz beinhalten. Aufrufe statischer Methoden sind demnach u.a. erlaubt.

Vor dem super(…)-Aufruf ist es nun möglich, wie in der oben gezeigten Klasse eine Validierung durchzuführen. Bisher hätte man die Validierung entweder erst nach dem super-Aufruf gemacht und im Fehlerfall ggf. unnötige Arbeit ausgeführt; die Alternative wäre, val im super-Aufruf in eine Validierungsmethode zu wrappen. Demgegenüber ist der oben gezeigte Code sowohl effizient als auch leicht nachvollziehbar.

Für this(…)-Aufrufe gilt dasselbe, wie das nachfolgende Beispiel zeigt:

JEP-457: Class-File API (Preview)

Im Java-Ökosystem ist das Parsen, Generieren und Transformieren von Klassendateien gang und gäbe, z.B. in Tools oder Frameworks, die dynamisch Bytecode analysieren, modifizieren oder generieren. Dafür nutzen sie Klassendateibibliotheken wie ASM, BCEL oder Javassist. Auch das JDK selbst hat eine eigene auf ASM basierende Klassendateibibliothek.

Neue Java-Sprachfunktionen und JVM-Eigenschaften führen zu Veränderungen im Klassendateiformat. Aufgrund des halbährlichen Releasezyklus von Java ändert sich allerdings auch das Klassenformat häufig, so dass die Klassendateibibliotheken sowie darauf aufbauende Frameworks es schwer haben, mit den Änderungen Schritt zu halten.

Mit diesem JEP soll die Java-Plattform eine Standard-API für Klassendateien definieren und implementieren, die sich zusammen mit dem Format der Klassendateien weiterentwickelt. Frameworks und Tools die diese API nutzen können so schneller die jeweils neueste JDK-Version unterstützen. Ein weiteres Ziel ist es, die Abhängigkeit des JDK zu ASM loszuwerden.

Da sich dieser Blogbeitrag vor allem an Anwendungsentwickler richtet, die entsprechende Frameworks nutzen, aber nicht selbst entwickeln, verzichte ich hier auf Codebeispiele und verweise auf die Beschreibung und Beispiele im JEP-457.

JEP-463: Implicitly Declared Classes and Instance Main Methods (Second Preview)

Um Einsteigern das Lernen von Java so einfach wie möglich zu machen, sieht das einfachste Java-Programm mit diesem Preview-Feature wie folgt aus:

So kommt das erste Erfolgserlebnis schnell und unkompliziert, ohne dass man sich mit Konzepten wie Klassen, statischen Methoden oder Methodensichtbarkeit auseinandersetzen muss.

Bei der Auswahl der auszuführenden Methode geht die JVM wie folgt vor: Gibt es eine main-Methode mit Parameter String[] args, wird diese ausgeführt. Existiert eine solche Methode nicht, wird die Methode main() ohne Parameter ausgeführt, sofern vorhanden. Ob statisch oder nicht, spielt dabei keine Rolle.

JEP-464: Scoped Values (Second Preview)

Scoped Values sollen ein leichtgewichtiger Ersatz für ThreadLocal werden. ThreadLocals werden beispielsweise von Webframeworks verwendet, um Informationen einer Benutzersession bei der Abarbeitung von Serveranfragen im Rahmen eines Threads zu speichern. Insbesondere bei Virtual Threads, von denen es sehr viele geben kann im Gegensatz zu den herkömmlichen Threads, kann dieser Ansatz problematisch werden. Hätte bei einer Anzahl von Millionen Virtual Threads jeder eine eigene Kopie, würde dies einen signifikanten Speicherbedarf bedeuten.

Mit Scoped Values wird es möglich, dass mehrere Threads sich Daten wie z.B. die Benutzerinformationen teilen.

Zuerst wird im WebFramework eine ScopedValue-Instanz vom Typ UserSession instanziert (Zeile 3). Die Referenz ist statisch, so dass auch aus anderen Codeteilen darauf zugegriffen werden kann.

In der doRequest(…)-Methode wird die userSession aus dem Store geholt (Zeile 14). Dann wird sie an die where(…)-methode von ScopedValue übergeben (Zeile 16), und im Anschluss direkt run(…) aufgerufen (Zeile 17), wo die Bearbeitung an die service-Instanz weiter delegiert wird.

In der handle(…)-Methode der Klasse Service kann nun die Benutzersession via get() aus der ScopedValue-Instanz ausgelesen und genutzt werden.

Die in where(…) gesetzte User-Session(Z. 17) ist nur innerhalb des Scopes der run(…)-Methode gültig. Würde man in Zeile 18 versuchen, via USER_SESSION.get() auf die zuvor gesetzte userSession zuzugreifen, wäre das Ergebnis eine NoSuchElementException.

Zudem kann der gesetzte Wert innerhalb des Scopes nicht neu gesetzt werden, da ScopedValue keine set(…)-Methode hat. Dies vereinfacht die nebenläufige Programmierung, und trägt auch zu einer besseren Performance bei.

JEP-459: String Templates (Second Preview)

Bis auf eine technische Änderungin den Typen der Template-Expressions hat sich in der 2. Preview gegenüber er ersten nichts geändert.

Ausdrücke werden mit \{ eingeleitet und mit } geschlossen. Bei STR handelt es sich um einen StringTemplate-Processor, der die Ausdrücke auswertet und das Ergebnis an die jeweilige Stelle im String-Template einfügt.

Auch Textblöcke werden unterstützt, um beispielsweise JSON oder andere mehrzeilige Strings zu erzeugen:

Sehr praktisch ist, dass doppelte Anführungszeichen in String Templates nicht escaped werden müssen. Das Ergebnis sieht wie folgt aus:

Neben STR gibt es auch FMT, einen FormatProcessor, der das Formatieren anhand der Formatter-Spezifikation erlaubt:

Doch lediglich Werte in Strings zu parsen, ging den Designern des Features nicht weit genug. Bei Erzeugung von SQL-Statements besteht beispielsweise die Gefahr der SQL-Injection:

Mit dem Wert „Smith‘ OR p.last_name <> ‚Smith“ ließe sich ein Statement erzeugen, das alle Einträge von Person zurückliefert. Daher soll der TemplateProcessor-Mechanismus in Java es ermöglichen, verschiedene Prozessoren für unterschiedliche Einsatzgebiete bereitzustellen. Deswegen ist es möglich, eigene Prozessoren zu schreiben, die valide bzw. sichere Ergebnisse produzieren. Im obigen Fall könnte z.B. ein SQL-Prozessor die Anführungszeichen in den Ausdruckswerten escapen.

Das Ergebnis muss nicht unbedingt vom Typ String sein; somit wäre auch ein Prozessor denkbar, der als Ergebnis ein SQL-Query- oder JSON-Objekt hat.

Hier ein Beispiel für einen selbstgeschriebenen Template-Prozessor, der alle Werte spiegelt:

JEP-462: Structured Concurrency (Second Preview)

Structured Concurrency leitet sich aus dem einfachen folgenden Prinzip ab:

„Wenn eine Aufgabe in nebenläufige Subtasks aufgeteilt wird, kehren alle wieder zu demselben Ort zurück, nämlich dem Codeblock des Tasks.“

Dies ist bei der bisherigen z.B. mittels ExecutorService implementierten Nebenläufigkeit nicht gegeben.

Dieser JEP soll einen Ansatz für die nebenläufige Programmierung schaffen, bei dem die natürliche Beziehung zwischen Aufgaben und Teilaufgaben erhalten bleibt, was zu besser lesbarem, wartbarem und zuverlässigem nebenläufigem Code führt. In der Structured Concurrency werden Subtasks im Rahmen eines Tasks abgearbeitet. Der übergeordnete Task wartet auf die Ergebnisse und überwacht Fehlerfälle in den einzelnen Subtasks.

Die beiden Subtasks findUser() und fetchOrder() werden hier innerhalb eines gemeinsamen Scopes abgearbeitet. Mit der join()-Anweisung in Zeile 6 wird auf die Beendung aller Subtasks gewartet und im Anschluss die Ergebnisse zusammengeführt und zurückgegeben.

Die Shutdown-Policy „ShutdownOnFailure“ (Zeile 2) bewirkt beim ersten Auftreten einer Exception in einem der Subtasks, dass die anderen noch laufenden Subtasks abgebrochen und die Exception weiter propagiert wird. Diese Policy ist relevant, wenn die Ergebnisse aller Subtasks benötigt werden.

Daneben existiert auch noch die Shutdown-Policy „ShutdownOnSuccess“. Diese bewirkt, dass das Ergebnis des ersten erfolgreichen Subtasks übernommen wird und alle anderen noch laufenden Subtasks abgebrochen werden. Diese Policy verwendet man, wenn nur das Ergebnis von irgendeinem der Subtasks benötigt wird:

In Java 22 ist die Structured Concurrency zum zweiten Mal als Preview-Feature vorhanden, ohne Änderungen gegenüber der ersten Preview, um weiteres Feedback aus der Community einzusammeln.

JEP-460: Vector-API (Seventh Incubator)

Mit der Vector API führt Java die plattformübergreifende Unterstützung zur Entwicklung datenparalleler Algorithmen ein. Anhand eines „Single Instruction Multiple Data“ (SIMD) Modells sollen die Vector-Instruktionen unterschiedlicher CPU-Architekturen unterstützt werden. Rechenoperationen (instructions) werden dabei statt auf nur einen Wert auf mehrere Werte gleichzeitig in nur einem CPU-Zyklus ausgeführt. Anwendungsfälle hierfür sind etwa die Bild- und Videoverarbeitung.

Codebeispiel:

Die Vector-API ist numehr in der siebten Incubator-Version enthalten, mit Performanceverbesserungen und Bugfixes gegenüber der Vorversion, sowie der Erweiterung, dass MemorySegments nun Arrays aller nativen Datentypen unterstützt statt nur Arrays vom Typ byte.

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*