Hallo zusammen,
im letzten Artikel haben wir die großen Schritte von Java 24 und Java 25 beleuchtet – von Stream Gatherers
bis zu Scoped Values
. Jetzt ist es Zeit, sich die Ärmel hochzukrempeln. Die wahre Stärke dieser Releases liegt in den kleinen, aber mächtigen Verbesserungen der Sprache, die unseren Code kürzer, sicherer und ausdrucksstärker machen.
Wir tauchen in die Code-Beispiele für die finalisierten und fortgeschrittenen Preview-Features ein.
1. Pattern Matching für switch
und instanceof
(Fortgesetzt)
Das Pattern Matching in Java wird von Version zu Version mächtiger. Es macht unseren Code, der auf Typen oder Werte prüft, unglaublich viel sauberer.
Vorher (Alt, Pre-Java 21):
Um einen Typ zu prüfen und dann zu casten, war viel Boilerplate nötig:
// Pruefen und Casten mit instanceof
public static void process(Object obj) {
if (obj instanceof String) {
String s = (String) obj; // Manueller Cast
System.out.println("String Laenge: " + s.length());
} else if (obj instanceof Integer) {
Integer i = (Integer) obj;
System.out.println("Integer Wert: " + i * 2);
}
}
Jetzt (Java 24/25 Preview, Pattern Matching):
Mit dem fortgeschrittenen Pattern Matching (insbesondere dem erweiterten instanceof
und switch
) wird dies viel eleganter. Man spricht vom Pattern Matching for Primitives, das in Java 25 weiter gereift ist.
// Pattern Matching fuer switch (erweitert in Java 25 Preview)
public static void process(Object obj) {
switch (obj) {
case String s -> System.out.println("String Laenge: " + s.length());
case Integer i -> System.out.println("Integer Wert: " + i * 2);
// Pruefung auf null ist direkt moeglich
case null -> System.out.println("Eingabe war null.");
default -> System.out.println("Unbekannter Typ.");
}
}
Die neue Syntax reduziert nicht nur Code, sondern macht auch Pattern Matching für primitive Typen (wie int
, long
) möglich, was die gesamte Typ-Analyse in Java vereinheitlicht.
2. Stream Gatherers
(Java 24 Finalisiert)
Wie bereits erwähnt, revolutionieren Stream Gatherers
die Zwischenoperationen in Stream-Pipelines, indem sie neue, komplexe Operationen wie “Fenster” (Grouping in fixed or sliding blocks) ermöglichen.
Das Problem vorher:
Um beispielsweise die Elemente eines Streams in Batches (Gruppen) zu verarbeiten, musste man oft den Stream verlassen, eine Collector
-Lösung verwenden oder manuell iterieren.
Die elegante Lösung (Java 24):
Das Gatherers.windowFixed(n)
ist ideal für die Batch-Verarbeitung von Daten, z.B. vor dem Senden an eine externe API.
import java.util.stream.Gatherers;
import java.util.stream.Collectors;
List<Integer> numbers = List.of(10, 20, 30, 40, 50, 60, 70);
List<List<Integer>> batches = numbers.stream()
// Erstellt feste Fenster der Groesse 3
.gather(Gatherers.windowFixed(3))
.collect(Collectors.toList());
// batches enthaelt: [[10, 20, 30], [40, 50, 60], [70]]
// Beispiel fuer die Verwendung des letzten Elements (folding/aggregation)
Optional<String> folded = numbers.stream()
.map(String::valueOf)
.gather(Gatherers.fold(() -> "", (acc, element) -> acc + element))
.findFirst();
// folded enthaelt: "10203040506070" (Alle Elemente zusammengefaltet)
Das ist ein massiver Gewinn an Ausdrucksstärke und ermöglicht es uns, komplexe Logik innerhalb der vertrauten Stream-API abzubilden.
3. Scoped Values (JEP 506, Java 25 Finalisiert)
Scoped Values
lösen die Probleme von ThreadLocal
in modernen Java-Anwendungen, insbesondere im Kontext von Virtual Threads (Loom). Sie machen das implizite Sharing von Daten sicher, unveränderlich und performant.
Das Problem mit ThreadLocal
in Virtual Threads:
ThreadLocal
kann in Loom-Anwendungen langsam sein und Speicherprobleme verursachen, da jeder Virtual Thread eine Kopie des ThreadLocal
-Wertes halten müsste, selbst wenn er nicht benötigt wird. Außerdem ist der Wert veränderbar (mutable
), was zu Race Conditions führen kann.
Die saubere Lösung (Java 25):
Scoped Values
sind an einen Ausführungskontext und nicht an einen physischen Thread gebunden und sind unveränderlich (immutable
).
// 1. Deklaration (sollte immer static final sein)
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();
public void handleRequest(String id) {
// 2. Wert binden (immutable) und Scope definieren
ScopedValue.where(REQUEST_ID, id)
.run(() -> businessLogic());
}
public void businessLogic() {
// 3. Aufruf in tief verschachtelten Methoden ohne Parameteruebergabe
logContext();
// ... Aufrufe, auch ueber Virtual Threads hinweg, im selben Scope
}
public void logContext() {
// 4. Wert sicher abrufen
String currentId = REQUEST_ID.get();
System.out.println("Verarbeite Anfrage mit ID: " + currentId);
}
// Aufruf:
// handleRequest("tx-456");
// Ausgabe: "Verarbeite Anfrage mit ID: tx-456"
Dieses Muster ist der Standard für das Weitergeben von Tracing-IDs, User-Sessions oder Konfigurationen in asynchronen und nebenläufigen Microservices. Es ist ein notwendiges Fundament für die Zukunft der Server-Side Java-Entwicklung.
Fazit
Java 24 und Java 25 liefern uns keine revolutionär neuen Paradigmen, sondern verfeinern die Sprache und die Plattform in Schlüsselbereichen:
- Code-Expressivität (
Pattern Matching
,Gatherers
) - Performance & Startzeit (
AOT Profiling
) - Nebenläufigkeit & Sicherheit (
Scoped Values
)
Indem wir diese Features in unsere täglichen Projekte integrieren, machen wir unseren Code nicht nur konform zur neuesten Java-Version, sondern auch fundamental besser, robuster und zukunftssicher. Es ist der perfekte Zeitpunkt, um von diesen Verbesserungen zu profitieren und das volle Potenzial des modernen Java auszuschöpfen.