Java 21: Die wichtigsten Features seit Version 17

Java 21, die neue Version mit verlängertem Support, ist da! Hier stelle ich die wichtigsten Features vor:

  • Interface SequencedCollection,
  • Record Patterns,
  • Neuerungen bei switch und
  • das Highlight virtuelle Threads

Falls Ihr noch Java 11 verwendet, schaut euch hier die Features von Java 17 an: java-17-features.html
🎓 Auf Udemy findet ihr meinen kostenloses Online-Kurs zu Java 21.

Java 21 at YouTube in English

Interface SequencedCollection

Die neuen Sequenced-Interfaces erweitern Listen-Implementierungen um den Direktzugriff auf das erste und letzte Element. Am Beispiel einer ArrayList zeige ich hier die neuen, selbsterklärenden Methoden:
// Mutable list created.
List<String> list = new ArrayList<>(List.of("1st", "2nd", "3rd"));

log.info("Read first & last element in list: %s & %s"
.formatted(list.getFirst(), list.getLast()));

var reversedList = list.reversed();
reversedList.addFirst("4th");
log.info("Reversed order with new first element: " + reversedList);
log.info("Changed list: " + list);

list.removeFirst();
list.removeLast();
list.addFirst("begin");
list.addLast("end");
log.info("Changed list: " + list);

Log-Ausgabe zum Code:

INFO: Read first & last element in list: 1st & 3rd
INFO: Reversed order with new first element: [4th, 3rd, 2nd, 1st]
INFO: Changed list: [1st, 2nd, 3rd, 4th]
INFO: Changed list: [begin, 2nd, 3rd, end]

Die eingesetzten Methoden kommen mit dem Interface SequencedCollection. Seit Java 21 gibt es analoge Interfaces als Spezialisierung von Map und Set, schaut euch dazu den JEP 431 an.

Trotz der komfortablen Methoden der SequencedCollection kann es in folgenden Fällen zu RuntimeExceptions kommen:

  • NoSuchElementException - wenn die Liste leer ist und ihr das erste oder letzte Element lesen oder entfernen wollt.
  • UnsupportedOperationException - wenn die Liste unveränderlich ist und ihr das erste oder letzte Element entfernen oder hinzufügen wollt. Eine unveränderliche Liste wird z. B. so erzeugt List.of("test"). Im Code-Beispiel erzeuge ich daher eine veränderliche ArrayList.
  • NullPointerException - wenn die Listenimplementierung das Hinzufügen von null nicht erlaubt.
Auch aus Clean Code Sicht ein tolle Erweiterung, da die neuen teils Parameter-losen Methoden leicht verständlich sind. Sie vermeiden auch Bugs bzgl. falscher Indexierung beim Zugriff auf das letzte Element einer Liste: list.size() vs. (list.size() - 1).

Pattern Matching bei switch

Mit Java 21 ist das Pattern Matching in switch Blöcken fester Bestandteil der Sprache, siehe dazu JEP 441. Pattern Matching in switch funktioniert analog zum Pattern Matching nach instanceof. Das case Label definiert mehr als eine einfache Konstante. Es kann sowohl Casten als auch Boolean-Ausdrücke auswerten. Die Auswertung eines Boolean-Ausdrucks im case benötigt zusätzlich das neue Keyword whennull als case Label ist jetzt möglich und macht null-Checks vor dem switch überflüssig. Hier ist ein Beispiel mit allen genannten Features:
BaseStream stream = DoubleStream.of(1.1);
switch (stream) {
case null -> log.info("null is now a possible case.");
case IntStream is when is.isParallel() ->
log.info("Expression in case.");
case DoubleStream ds -> log.info("Casted in case.");
default -> throw new IllegalStateException();
}

Record Patterns

Pattern Matching ist auch mit Records möglich. Das wurde mit JEP 440 in Java 21 eingeführt. Mehr zu Records findet ihr hier. Das Besondere am Record Patterns ist der direkte Zugriff auf die Record-Attribute beim Casten (siehe instanceof und case unten). Im folgenden Code Beispiel gibt es 2 verschiedene Records. Beim Pair-Record caste ich im if und switch Statement jeweils auf die beiden Attribute und verkürze ihren Namen dabei auf k und v.
record Pair(String key, Integer value){}
record Single(Double element){}

private static void recordPatterns() {
var mixedList = List.of(
new Single(1.1),
new Pair("2nd", 2));

for (Object entry : mixedList) {
if (entry instanceof Pair(String k, Integer v))
log.info("Record has: %s %d".formatted(k, v));
switch (entry) {
case Single s -> log.info("Record: " + s);
case Pair(String k, Integer v) -> log.finest("...");
default -> throw new IllegalStateException();
}
}
}
Log-Ausgabe zum Code:
INFO: Record: Single[element=1.1]
INFO: Record has: 2nd 2

Virtuelle Threads

Endlich - virtuelle Threads sind fester Bestandteil von Java! JEP 444 integriert die leichtgewichtigen Threads in die Java Plattform. Praktischer Weise unterscheidet sich das Erstellen virtueller Threads kaum vom Erstellen klassischer Threads:
try (var executorService = Executors.newVirtualThreadPerTaskExecutor()) {
executorService.submit(() -> {
var virtualThread = Thread.currentThread();
log.info(virtualThread.threadId() + " : "
+ virtualThread.isVirtual());
});
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
  • Die Log-Ausgabe zum Code sieht so aus:
    INFO: 22 : true
  • Mit der neuen Methode isVirtual überprüfen wir, ob wirklich ein virtueller Thread erzeugt wurde. Der hier gezeigt Thread loggt nur seine eigene ID und ob er virtuell ist.
  • Zum Erzeugen neuer Threads wird ein ExecutorService mit Executors.newVirtualThreadPerTaskExecutor instanziiert. Dieser Service erzeugt neue virtuelle Threads mit der Methode submit. Somit können virtuelle Threads analog zu klassischen Thread erzeugt werden - am ExecutorService Interface hat sich nichts geändert.
  • Das hier verwendete try-with bedient das AutoCloseable Interface, um den ExecuterService automatisch zu schließen.
Virtual Threads at YouTube in English

Server Anwendungen profitieren von dem nun möglichen Thread-pro-Request Stil. So kann beispielsweise eine mit dem Spring Framework gebaute API, für jeden Request einen eigenen virtuellen Thread erzeugen. Das ermöglicht eine nahezu optimale Hardware-Ausnutzung, wie wir es bisher nur von reaktiven Anwendungen kannten.

Fazit

2 Jahre nach Java 17 gibt es ein neues LTS (Long Term Support) Release. Dieses erweitert die Sprache Java um wenige, aber gute Sprach-Features. Virtuelle Threads sind für mich das Highlight. Sie helfen unter der Last vieler, parallel eingehender Requests eine bessere Hardware-Nutzung zu erreichen - das spart Geld und Strom!

Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

Authentifizierung in Web-Anwendungen mit Spring Security 6

Reaktive REST-Webservices mit Spring WebFlux