Programmiersprache Java 23 erweitert Pattern Matching und Import von Klassen

Neben weiterentwickelten Preview-Features wie der Class-File API bringt das JDK Neuerungen beim Pattern Matching und öffnet die Dokumentation für Markdown.

In Pocket speichern vorlesen Druckansicht 41 Kommentare lesen

(Bild: Erstellt mit Dall-E von iX-Redaktion)

Lesezeit: 9 Min.
Inhaltsverzeichnis

Turnusmäßig im Sechsmonatstakt ist das OpenJDK 23 erschienen. Das Release bringt wie der Vorgänger zwölf Java Enhancement Proposals (JEP), von denen sich die meisten in der Preview-Phase befinden.

Viele JEPs führen die in Java 21 und Java 22 gestarteten Proposals fort. Darüber hinaus finden sich zwei erste Previews im aktuellen JDK. Neben Neuerungen an der Sprache und den Libraries gibt es auch Erweiterungen für die Dokumentation und Verbesserungen beim Garbage Collector. Schließlich läutet das JDK das Ende vom unsicheren Speicherzugriff über sun.misc.Unsafe ein.

String Templates fehlen allerdings in Java 23 und sollen erst später in einem überarbeiteten Design wieder in die Sprache einfließen.

Die vorherigen Java-Versionen brachten einige Neuerungen für das Pattern Matching, die einen eleganten Umgang beim Verarbeiten von Datenstrukturen ermöglichen. "JEP 455: Primitive Types in Patterns, instanceof, and switch" ermöglicht als neue Preview erstmals auch den Einsatz primitiver Datentypen innerhalb des Pattern Matching. Damit sind unter anderem in case-Bedingungen nicht nur direkte Zahlenwerte oder Referenztypen wie Integer und String, sondern primitive Typen wie byte oder int erlaubt, wie in folgendem Beispiel aus dem Proposal:

switch (x.getYearlyFlights()) {
  case 0 -> ...;
  case 1 -> ...;
  case 2 -> issueDiscount();
  case int i when i >= 100 -> issueGoldCard();
  case int i -> //... appropriate action when i > 2 && i < 100 ...
}

Der Typ der Variable im switch-Statement muss nicht den Datentypen in den einzelnen Bedingungen entsprechen. Analog dazu ergibt instanceof unabhängig vom Variablentyp true, sofern sich die Variable sicher umwandeln lässt:

float f = 1000.0f;
f instanceof double; // true 
f instanceof int;    // true, da konvertierbar 
f instanceof byte;   // false, da jenseits des Raums von byte

Der Typ wird dabei nicht umgewandelt, aber das Ergebnis ist nur dann true, wenn eine verlustfreie Umwandlung möglich ist. Dasselbe gilt für Record Patterns.

Ebenfalls als erste Preview gekennzeichnet ist "JEP 476: Module Import Declarations (Preview)", das den Import von Paketen vereinfacht, indem es alle Pakete einbindet, die ein Modul exportiert. So stellt import java.base alle 54 in dem Modul definierten Pakete wie java.io, java.net und java.security bereit und erspart somit den separaten Import über die Einzelbefehle wie import java.io.*.

Potenzielle Unklarheiten müssen Anwendungen explizit auflösen. Beispielsweise existiert die Klasse Date sowohl in java.util, das im Modul java.base enthalten ist, als auch in java.sql. Bei einem Import beider Module müssen Anwendungen, die Date verwenden, den zu verwendenden Import angeben:

import module java.base;
import module java.sql; 

import java.sql.Date;   // bestimmt die zu verwendende Date-Klasse

Date d = new Date();    // verwendet die Klasse aus java.sql

Das "JEP 477: Implicitly Declared Classes and Instance Main Methods" erschien erstmals in Java 21 als "JEP 445: Unnamed Classes and Instance Main Methods". Es vereinfacht vor allem den Aufruf der main-Methode und erfordert weniger Boilerplate-Code. Unter anderem wird das klassische und bisher nicht wirklich minimalistische Hello-World-Programm deutlich vereinfacht:

//klassisches Hello World
public class HelloWorld { 
  public static void main(String[] args) { 
    System.out.println("Hello, World!");
  }
}

//minimalistisches Hello World mit JEP 477
void main() {
  println("Hello, World!");
}

Mehr Flexibilität im Konstruktor ist das Ziel von "JEP 482: Flexible Constructor Bodies". Es führt in der zweiten Preview das in Java 22 als "JEP 447: Statements before super(...)" eingeführte Proposal fort. Das JEP bricht mit der alten Regel, dass kein Code vor super erlaubt ist, wenn ein Java-Konstruktor den Konstruktor der Oberklasse aufruft.

Dank der Neuerung können Programme beispielsweise Parameter überprüfen, um nach dem Fail-Fast-Prinzip bereits vor dem Aufruf des Konstruktors der Oberklasse Fehler zu erkennen und Overhead zu vermeiden:

public class PositiveBigInteger extends BigInteger {

  public PositiveBigInteger(long value) {
    if (value <= 0) throw new IllegalArgumentException(..);
    super(value);
  }
}

Außerdem ist es nun möglich, komplexe Parameterberechnungen für den Konstruktor der Oberklasse in der Kindklasse durchzuführen.

Ebenfalls in Java 22 kamen erste Previews zum Verarbeiten von Klassen und Streams, die Java 23 fortführt. "JEP 466: Class-File API" bringt eine API, um die Dateien von Java-Klassen zu parsen, zu erstellen und umzuwandeln. Als Standard für den Core von Java soll sie class-Dateien ebenso verarbeiten können wie die Tools von Drittanbietern, darunter ASM oder Apache Commons BCEL (Byte Code Engineering Library). Dabei ist sie als einfache API ausgelegt, die die externen Libraries nicht ersetzen soll.

Die Heise-Konferenz zu Java

(Bild: anchalee thaweeboon/Shutterstock.com)

Am 15. Oktober findet die betterCode() Java 2024 statt. Die Online-Konferenz zeigt die wichtigsten Neuerungen von Java 21 bis Java 23.

Das Programm des Thementags besteht aus sieben Vorträgen zu folgenden Themen:

  • Die neuen Features von Java 23 im Überblick
  • String Templates in Überarbeitung
  • Stream Gatherers für eigene Stream-Operationen
  • Nebenläufige Programme in Java 23
  • Abschied vom JNI dank Foreign Function and Memory API
  • Mehr Einblick dank der Class-File API
  • Erweiterte Features für Fortgeschrittene

Das "JEP 473: Stream Gatherers" fließt ohne Änderungen im Vergleich zur ersten Preview in Java 22 in das aktuelle JDK ein, und für Java 24 ist unter JEP 485 die Finalisierung des Proposal geplant. Es erweitert die Java-Stream-API um zusätzliche Operationen, die das Transformieren von gestreamten Daten ermöglicht und dabei weitergeht als die bisherige Erweiterung über Stream::collect(). Dieser Collector kann aber weiterhin hinter dem Gatherer stehen, und mehrere Gatherer lassen sich in der Form stream.gather(...).gather(...).collect(...) kombinieren. Die Transformation über die Gatherer kann in 1:1-, 1:n-, n:1- oder n:m-Relation erfolgen.