Nach 21 kommt? Genau, 22! 💡 JDK 22 ist das letzte JDK Update, welches seit dem 19. März 2024 verfügbar ist.
In diesem TechUp wollen wir uns anschauen, was es Neues gibt und was sich geändert hat.
Selbstverständlich findest du alle Code-Beispiele in unserem TechHub GitHub JDK 22 Repository.
Solltest du den letzten LTS (Long Term Support) Release JDK 21 noch nicht kennen, dann schaue dir doch mein TechUp zu JDK 21 an.
Schauen wir uns nun einige der spannenden JEP - JDK Enhancement Proposals - an, die in JDK 22 enthalten sind.
Preview Features
Nachfolgend werden wir uns einige Preview-Features ansehen, die in JDK 22 enthalten sind.
Diese speziellen Features sind noch nicht vollständig und können sich in zukünftigen Versionen ändern oder sogar entfernt werden.
Die können mit dem Flag -enable-preview
aktiviert werden. Alternativ erkennt z.B. IntelliJ die Verwendung von Preview-Features und schlägt vor, das Flag zu setzen.
423: Region Pinning for G1
Ein sehr technisches Feature, welches die Garbage Collection verbessern soll. An dieser Stelle möchte ich nicht weiter darauf eingehen, da es sehr spezifisch ist und nicht für jeden Entwickler relevant ist. Weitere Informationen findest du hier.
447: Statements before super(…) (Preview)
Bei JEP-447 handelt es sich um ein Preview-Feature, um Logik vor dem Aufruf des Super-Konstruktors zu platzieren.
In Java 8 wurde die Möglichkeit eingeführt, dass Konstruktoren von Subklassen den Konstruktor der Superklasse aufrufen können. Allerdings musste dies als erste Anweisung im Konstruktor geschehen. Mit JEP-447 wird diese Einschränkung aufgehoben und es ist nun möglich, Anweisungen vor dem Aufruf des Superkonstruktors zu platzieren.
Bisher musste das immer wie folgt aussehen:
|
|
Dadurch ergeben sich teilweise umständliche Aufrufe, wie beispielsweise:
|
|
In diesem Beispiel ist zu sehen, dass die Superklasse unnötigerweise initialisiert wird, obwohl der Name noch gar nicht geprüft wurde.
Ist der Name null
, so würde eine Exception geworfen werden. Die Initialisierung der Superklasse war in diesem Fall unnötig.
Mit JEP-447 kann dies nun wie folgt aussehen:
|
|
Das war bisher in einer abgespeckten Art und Weise, mit sogenannten Hilfs-Methoden möglich, allerdings nur auf einer Zeile innerhalb des super-Konstruktor-Aufrufs.
Das ist nicht nur bei Input-Validation praktisch, sondern auch, wenn die Parameter vorbereitet werden müssen für den Super-Konstruktor.
Beispielsweise wäre eine solche Logik neu einfacher zu implementieren:
|
|
Hier ist schön zu sehen, dass wir erst den Input Parameter prüfen, anschliessend entsprechend darauf reagieren und dann, mit diesem Resultat, den Super-Konstruktor aufrufen.
Das ganze Programm findest du im Repo!
Wichtig zu beachten ist, dass nur static fields und static methods vor dem Super-Konstruktor-Aufruf aufgerufen werden können. Eigentlich logisch, da die Instanz ja noch nicht erstellt wurde.
454: Foreign Function & Memory API
Diese neue API erlaubt es, aus einer Java-Anwendung heraus, native Funktionen aufzurufen und Speicher zu verwalten.
Auch hier wollen wir nicht genauer auf JEP-454 eingehen, da es ein sehr spezifisches Feature ist und nicht für jeden Entwickler relevant ist.
456: Unnamed Variables & Patterns
Mit JEP-456 werden unbekannte Variablen eingeführt. 👻
Hierbei geht es um ungenutzte Variablen, die mit einem Unterstrich _
markiert werden können. Diese Variablen können in einem Pattern-Matching-Statement verwendet werden, ohne dass sie explizit genutzt werden müssen.
Schauen wir uns zuerst ein altes Beispiel an:
|
|
In dem Code ist zu sehen, dass die Exception ex
nicht verwendet wird. Mit JEP-456 kann dies nun wie folgt aussehen:
|
|
Zugegeben, das ist jetzt nicht die Welt, aber es zeigt, dass es in Zukunft möglich sein wird, ungenutzte Variablen zu markieren und somit den Code lesbarer zu machen.
Und es verbessert das Sonar Rating, wenn ungenutzte Variablen nicht mehr als Fehler angezeigt werden. 😉
Dies soll neu auch in anderen Building-Blocks von Java möglich sein, wie beispielsweise in switch
-Statements oder try-with-resources
-Statements.
457: Class-File API (Preview)
Bei JEP-457 handelt es sich um ein Preview-Feature, welches es ermöglicht, den Inhalt von Class-Dateien zu lesen und zu schreiben. Speziell geht es auch um das Parsing, Generieren und Transformieren von Java class files.
Mir stellt sich die Frage, ob und wann man das will? Dynamisch zur Laufzeit neue Java-Dateien generieren? Oder Java-Dateien zur Laufzeit verändern? Widerspricht das nicht dem Prinzip von Java, dass es eine statisch typisierte Sprache ist? Und dass der Code zur Compile-Zeit geprüft wird?
Hierbei handelt es sich um ein Preview-Feature, sollte das in zukünftigen Versionen von Java eine grössere Rolle spielen, so wird es sicherlich nochmals genauer angeschaut.
458: Launch Multi-File Source-Code Programs
Hierbei geht es darum, dass der Java Launcher automatisch erkennt, welche Abhängigkeiten ein Multi-File Source-Code Programm hat und diese automatisch lädt.
So kann mit JEP-458 ein Programm, welches andere Klassen importiert, direkt gestartet werden. Die importierten Klassen werden dann automatisch mit kompiliert und geladen.
459: String Templates (Second Preview)
Hier hat sich seit JDK 21 nicht viel geändert. Aus meiner Sicht weiterhin ein cooles Feature, alle Informationen findest du hier in meinem TechUp zu JDK 21.
Die Preview Features sind immer in unterschiedlichen Releases implementiert, daher ist es nicht verwunderlich, dass sich hier nicht viel getan hat.
460: Vector API (Seventh Incubator)
Bei JEP-460, der Vector API, handelt es sich sage und schreibe um das siebte Inkubator-Release. Dies bedeutet, der Release Candidate ist noch nicht fertig und es wird weiterhin daran gearbeitet. Das erste Mal wurde die Vector API in JDK 16 eingeführt und wird seitdem stetig weiterentwickelt.
Um was geht es? Die API liefert einen Standard, um Vektorberechnungen zu formulieren. Dies ist besonders nützlich, da spezielle Vektorprozessoren für eine parallele Verarbeitung von Vektoroperationen sorgen. Im Gegensatz zur klassischen, skalaren Berechnung kann dies speziell bei datenintensiven Aufgaben zu einer massiven Performancesteigerung führen.
Laut Dokumentation werden aktuell x64 and AArch64 CPUs unterstützt.
461: Stream Gatherers (Preview)
Die Stream-API, die vor 10 Jahren mit Java 8 eingeführt wurde, ist ein sehr mächtiges Tool in Java und hat sich seitdem stetig weiterentwickelt Mit JEP-461 wird die Stream-API um sogenannte Gatherers erweitert.
Gatherers sind eine Art von Collectors, die es ermöglichen, Streams zu sammeln und zu verarbeiten. Bisher war es nur möglich, Streams zu sammeln und zu verarbeiten, wenn alle Elemente des Streams verfügbar waren. So können beispielsweise unterschiedliche, eigen implementierte GroupBy-Operationen durchgeführt werden.
Schauen wir uns ein Beispiel an. Wir haben eine Liste von Wörtern und wollen diese immer in Gruppen von 4 zusammenfassen.
Bisher wäre dies beispielsweise wie folgt möglich gewesen:
|
|
Nicht wirklich elegant und nicht direkt ersichtlich, was hier passiert. Mit JEP-461 wird dies nun wie folgt möglich sein:
|
|
Deutlich einfacher, mit Gatherers.windowFixed(4)
wird die Liste in Gruppen von 4 zusammengefasst.
Die Ausgabe ist bei beiden Beispielen die gleiche:
|
|
Wichtig zu erwähnen ist hier, dass es sich bei Gathers um Intermediate-Operationen handelt, die nur in Streams verwendet werden können.
Grundlegend besteht ein Gather aus vier Teilen:
- Integrator - die Hauptfunktion, welche einen State, ein Element und ein DownStream entgegennimmt und einen boolean zurückgibt
- Initializer - die Funktion, welche den Initial-Zustand des Gatherers definiert, einfach gesagt handelt es sich hier um einen Supplier
- Finishers - die Funktion, welche den Zustand des Gatherers abschliesst und das Resultat zurückgibt, technisch ein BiConsumer
- Combiner - wird nur benötigt, wenn der Gatherer parallel verwendet wird, hier wird der Zustand zusammengeführt
Die Gatherers Klasse bietet neben windowFixed
noch weitere Methoden an.
Schauen wir uns ein paar Beispiele von selbst implementierten Gatherers an.
Simple Gatherer
Schauen wir uns einen einfacher Gatherer an, wo wir nur einen Integrator nutzen.
Selbstverständlich könnten wir hierfür auch eine normale map
-Operation verwenden, aber es zeigt, wie Gatherer funktionieren.
|
|
Zu sehen ist, dass innerhalb von unserem Integrator die Elemente in Grossbuchstaben umgewandelt und in den Stream geschrieben werden.
Mit return true
geben wir an, dass wir weitermachen wollen. Sobald wir false
zurückgeben, wird der Stream abgebrochen.
Stateful Streams
Wir haben nun eine Liste von unsortierten Zahlen und wollen immer nur Zahlen in unseren Stream haben, welche grösser sind als die Zahlen, welche wir bereits gesehen haben.
Das folgende Programm gibt [1, 2, 5, 6, 7, 11, 20]
aus.
|
|
- Zuerst legen wir unsere Liste an
- Dann initialisieren wir einen Stream (Source), rufen unseren Gatherer (Intermediate) auf und sammeln das Resultat (Terminal) und geben dies aus.
- Der Gatherer nimmt einen Comparator entgegen, welcher die Zahlen vergleicht.
- Anschliessend legen wir uns unseren Initializer an, welcher eine AtomicReference zurückgibt. Somit können wir den Zustand speichern.
- Innerhalb von unserem Gatherer-Integrator prüfen wir, ob die Zahl grösser ist als die bisher grösste Zahl. Ist dies der Fall, so wird die Zahl in den Stream geschrieben und als neue grösste Zahl gespeichert.
- Der Gatherer gibt immer
true
zurück, da wir immer weitermachen wollen. Würden wir hierfalse
zurückgeben, so würde der Stream keine weiteren Elemente mehr verarbeiten. - Den Gatherer erstellen wir mit
Gatherer.ofSequential(initializer, integrator)
Kein einfaches Beispiel, aber es zeigt, wie mächtig Gatherer sein können.
Stateful mit Finisher
Nehmen wir nochmals das vorherige Example, wir wollen die letzte Zahl, welche in der Liste ist multiplizieren mit 2. Im Integrator können wir das nicht tun, da wir nicht wissen, ob es die letzte Zahl ist. Daher müssen wir dies im Finisher machen.
Das folgende Programm gibt [1, 2, 5, 6, 7, 11, 20, 40]
aus.
|
|
Hierfür nutzen wir nun einen vollständig typisierten Gatherer, welches einen Zustand, ein Element und einen Downstream entgegennimmt.
Im Finisher sehen wir, dass wir auf das letzte Element zugreifen können und dieses verarbeiten können. Wir können auch den downstream verändern, wenn wir wollen.
Spannendes Feature, welches die Stream-API nochmals erweitert und neue Möglichkeiten bietet.
462: Structured Concurrency (Second Preview)
Bei JEP-462 handelt es sich um ein Preview-Feature, welches es ermöglicht, asynchrone Operationen in einer strukturierten Art und Weise zu verwalten. So können zusammenhängende Tasks, welche parallel in unterschiedlichen Threads laufen, in einer Gruppe zusammengefasst und verwaltet werden. Das macht speziell das Error Handling und das Abbrechen von Tasks einfacher.
Schauen wir uns zuerst ein klassisches Beispiel an:
|
|
Beide Threads werden unabhängig voneinander gestartet und laufen parallel. Das Resultat wird erst dann zusammengeführt, wenn beide Threads fertig sind. Es gibt keine logische Verknüpfung zwischen den beiden Threads, sollte ein Thread fehlschlagen, so wird der andere Thread weiterhin ausgeführt.
Neu führt JEP-462 die Klasse StructuredTaskScope
ein, welche in einem try-with-resources
-Block verwendet werden kann.
Beim Starten wird neu ein Supplier
definiert und nicht mehr ein Future Objekt.
|
|
Schön zu sehen ist, dass mit scope.join() beide Subtasks zusammengeführt werden und mit throwIfFailed()
Fehler propagiert werden.
So würde ein Fehler in einem der Subtasks dazu führen, dass der andere Subtask abgebrochen wird, sollte dieser noch laufen.
Die Ausgabe der Logs ist identisch, spannend wird es, wenn unsere Subtasks fehlschlagen.
Error Handling
Gehen wir nun davon aus, dass unsere beiden Methoden wie folgt implementiert sind:
|
|
Tom wird nach 5 Sekunden gefunden, Tim wird nicht gefunden und wirft eine Exception.
Unser Output in der alten Variante ist:
|
|
Schön zu sehen ist, dass Tom gefunden wurde, Tim nicht und der Fehler korrekt ausgegeben wird. Das Programm benötigt aber die kompletten 5 Sekunden, bis es den Fehler erkennt.
Mit der neuen Variante wird der Fehler sofort erkannt und das Programm bricht ab:
|
|
Hier ist schön zu sehen, dass Tom nicht gefunden wird, weil der vorher verknüpfte Task fehlschlägt und daher der Thread sofort abgebrochen wird.
Das Feature bietet aber noch weitere Möglichkeiten, wie beispielsweise bestimmte custom shutdown policies
oder das Processing von parallelen Results in einem Stream.
Cooles Feature, welches das Error Handling in parallelen Java Programmen nochmals verbessert. 🚀 Ich bin gespannt, ob und wie dieses Feature Einzug in Libraries wie Spring oder Quarkus halten wird.
463: Implicitly Declared Classes and Instance Main Methods (Second Preview)
Hier hat sich seit JDK 21 nichts geändert. Aus meiner Sicht weiterhin ein hilfreiches Feature, alle Informationen findest du hier in meinem TechUp zu JDK 21.
464: Scoped Values (Second Preview)
Und last but not least, JEP-464 - Scoped Values.
Hierbei handelt es sich um die second Preview-Version, welche es ermöglicht, Werte in einem bestimmten Scope zu speichern und abzurufen.
Konkret wird ein neuer ScopedValue
-Type eingeführt, welcher immutable ist und es so einfacher macht, Informationen mit Child-Frames im gleichen Thread oder gar mit Sub-Threads zu teilen.
In der Vergangenheit musste man hierfür ThreadLocal
Variablen verwenden, um auf beispielsweise Request
-Informationen zuzugreifen, wenn diese nicht Teil der Methodensignatur waren.
Neu soll das via ScopedValue
möglich sein, welches als Instanzvariable angelegt werden kann.
Schauen wir uns ein einfaches Beispiel an:
|
|
Zuerst legen wir uns ein ScopedValue
an, welches den Namen speichert.
In unserer main-Methode setzen wir den Wert auf Tom
und rufen anschliessend die greet
-Methode auf.
Hier ist schön zu sehen, dass der Aufruf von greet
im gleichen Scope stattfindet und somit auf den Wert Tom
zugreifen kann.
In der Methoden können wir dann auf das ScopedValue zugreifen und den Wert auslesen.
Nun wird es spannend, wir passen das ScopedValue an!
|
|
Was meinst du was kommt heraus? Wir haben eine neue Methode goodbye
, welche den Wert aus dem ScopedValue ausliest und ausgibt.
In der greet
-Methode setzen wir, nach dem Begrüssen von Tom, den Wert auf Tim
und rufen anschliessend die goodbye
-Methode auf.
Anschliessend rufen wir aber nochmals die goodbye
-Methode auf.
Die Ausgabe ist recht spannend, und eigentlich ganz logisch:
|
|
Zuerst wird Hello Tom!
ausgegeben, da wir den Wert auf Tom
gesetzt haben. Der Wert ist für den Scope gültig, in welchem wir uns befinden.
Dann starten wir aber einen neuen Scope und setzen diesen Wert auf Tim
. In diesem Scope ist der Wert Tim
gültig.
Anschliessend rufen wir aber goodbye
nochmals auf, ohne einen neuen Scope zu starten. Daher wird hier der Wert Tom
ausgegeben.
Eine weitere Möglichkeit, welche uns die ScopedValue API bietet, ist das Empfangen von Werten aus dem entsprechenden, scoped Aufruf.
|
|
Hier sehen wir, dass unsere getName
-Methode den Wert aus dem ScopedValue ausliest und diesen in Grossbuchstaben zurückgibt.
In unserer main Methoden rufen wir die getName
-Methode auf und speichern das Resultat in einer Variable, diese Variable wird dann ausgegeben.
Auch hier haben wir wieder die Möglichkeit, mehrere Scopes zu starten und zu beenden, um unterschiedliche Werte zu setzen.
|
|
Selbstverständlich können wir auch mehr als nur ein ScopedValue nutzen.
|
|
Cool, oder? Das ist ein sehr mächtiges Feature, welches das Arbeiten mit Threads und Scopes nochmals vereinfacht. 🌶️
Fazit
JDK 22 ist der erste Release nach einem LTS Release und bringt wie erwartet ein paar hilfreiche Features, die das Entwickeln in Java noch einfacher machen. Anfangs war ich skeptisch, ob die Preview-Features wirklich so hilfreich sind, aber ich muss sagen, dass ich positiv überrascht bin. Gerade die Stream-Gatherer und die Scoped-Values sind sehr mächtige Features, die das Entwickeln in Java nochmals vereinfachen. Aber auch die Structured Concurrency und die Statements vor dem Super-Konstruktor sind nicht zu unterschätzen und können sich als hilfreich erweisen.
Das nächste TechUp zu JDK 23 wird sicherlich spannend, ich bin gespannt, was uns da erwartet, stay tuned!