Von Unbenannten Mustern über Pattern Matching für primitive Typen bis hin zu flexibleren Konstruktoren – die Java-Versionen 21 bis 25 bringen tiefgreifende Änderungen. 

 

Die Evolution von JDK 21 zu 25

Die Entwicklung von Java schreitet in einem bemerkenswerten Tempo voran. Seit der Veröffentlichung von Java 21 hat eine Reihe von Java Enhancement Proposals (JEPs) den Weg in die nachfolgenden JDK-Versionen gefunden, die bis zur Preview von JDK 25 reichen. Diese Neuerungen konzentrieren sich auf drei Kernbereiche: eine ausdrucksstärkere, datenorientierte Programmierung, eine signifikante Vereinfachung des Einstiegs in die Sprache und fundamentale Verbesserungen im Umgang mit Immutabilität und der Objekterstellung. Für Entwickler bedeutet dies saubereren, sichereren und oft auch performanteren Code.

 

Kurz & Knapp: Die wichtigsten Änderungen

  • Unbenannte Muster (_): Erhöhe die Lesbarkeit Deines Codes, indem Du explizit Variablen ignorierst, die nicht benötigt werden.
  • Pattern Matching für Primitiva (Preview): Nutze die Mächtigkeit von switch-Ausdrücken nun auch für int, double und Co. und führe sichere Typkonvertierungen durch.
  • Vereinfachte Main-Methode: Verabschiede Dich vom Boilerplate public static void main(String[] args) und schreibe direkt lauffähigen Code.
  • Javadoc mit Markdown: Verfasse Deine API-Dokumentation in der bekannten und einfachen Markdown-Syntax direkt im Code.
  • Flexible Konstruktoren: Erhalte mehr Kontrolle über die Initialisierungsreihenfolge und validiere Argumente, bevor teure super()-Konstruktoren aufgerufen werden.

Datenorientierte Programmierung: Mehr Ausdruckskraft und Sicherheit

Die jüngsten Java-Versionen legen einen starken Fokus darauf, die Verarbeitung von Datenstrukturen zu verfeinern. Dies manifestiert sich in zwei wesentlichen Erweiterungen des Pattern Matchings.

Unbenannte Muster und Variablen: Wenn weniger mehr ist

Eine der subtilsten, aber wirkungsvollsten Neuerungen ist die Einführung von unbenannten Mustern und Variablen, repräsentiert durch den Unterstrich (_). Immer dann, wenn eine Variable deklariert werden muss, ihr Wert jedoch im weiteren Verlauf des Codes keine Rolle spielt, kann der Unterstrich als Platzhalter dienen. Dies reduziert nicht nur visuelles Rauschen, sondern verhindert auch aktiv Fehler, da eine unbeabsichtigte Verwendung der ignorierten Variable vom Compiler unterbunden wird.

 

Besonders nützlich ist dies bei der Dekonstruktion von Records. Benötigst Du nur eine Komponente eines Records, können die anderen einfach ignoriert werden:

// Anstatt alle Komponenten zu deklarieren
if (response instanceof Response(int statusCode, String message)) {
    System.out.println("Nachricht: " + message);
}

// Kann die unnötige 'statusCode'-Variable ignoriert werden
if (response instanceof Response(_, String message)) {
    System.out.println("Nachricht: " + message);
}

Auch in catch-Blöcken, in denen lediglich die Tatsache des Exceptions-Eintritts relevant ist, nicht aber das Exception-Objekt selbst, sorgt der Unterstrich für klarere Intentionen:

try {
    int result = Integer.parseInt("invalid");
} catch (NumberFormatException _) {
    // Das Exception-Objekt 'e' wird nicht benötigt
    logError("Ungültiges Zahlenformat erhalten.");
}

Ein wichtiger technischer Hinweis bei der Verwendung mit Record Patterns: Die Accessor-Methoden der ignorierten Komponenten werden aus Gründen der Abwärtskompatibilität weiterhin aufgerufen. Eine mögliche Performance-Optimierung, bei der diese Aufrufe entfallen, kann nur zur Laufzeit durch die JVM erfolgen, falls diese feststellt, dass die Accessor-Methode frei von Seiteneffekten ist.

Pattern Matching für primitive Typen: Die Revolution im switch (Preview in JDK 25)

Mit JEP 507, das sich in JDK 25 in der dritten Preview-Phase befindet, wird eine langjährige Einschränkung aufgehoben: Pattern Matching ist nicht länger auf Referenztypen beschränkt. Nun können auch primitive Typen wie int, double oder boolean in instanceof- und switch-Ausdrücken verwendet werden.

 

Dies ermöglicht eine deutlich elegantere und lesbarere Abfrage von Wertebereichen. Ein klassisches if-then-else-Konstrukt zur Überprüfung von HTTP-Statuscodes kann nun durch einen prägnanten switch-Ausdruck ersetzt werden:

String interpretStatusCode(int code) {
    return switch (code) {
        case 200, 201, 204 -> "Erfolgreich";
        case 400, 401, 403 -> "Client-Fehler";
        case 404 -> "Nicht gefunden";
        case 500, 502, 503 -> "Server-Fehler";
        default -> "Unbekannter Status";
    };
}

Die wahre Stärke zeigt sich jedoch bei der sicheren Typkonvertierung. Bei der Verarbeitung von Datenformaten wie JSON, bei denen Zahlen oft als double repräsentiert werden, kann nun direkt auf einen int gematcht werden. Die Konvertierung gelingt nur, wenn sie ohne Präzisionsverlust möglich ist. Dies ist ein erheblicher Sicherheitsgewinn gegenüber einem einfachen Cast.

Object jsonValue = 42.0;

// Sicherer Match auf int, gelingt nur bei verlustfreier Konvertierung
if (jsonValue instanceof int i) {
    // Funktioniert, da 42.0 exakt als 42 repräsentiert werden kann
    processAge(i);
}

Object anotherJsonValue = 42.5;

// Dieser Match schlägt fehl, da 42.5 nicht präzise in int passt
if (anotherJsonValue instanceof int i) {
    // Dieser Block wird nicht ausgeführt
}

Vereinfachungen im Anwendungsdesign: Java für den schnellen Einstieg

Ein weiterer Schwerpunkt der jüngsten Entwicklungen ist die Reduzierung von Boilerplate-Code, was insbesondere Neulingen den Einstieg in die Java-Welt erleichtert.

Die neue main-Methode: Kein Boilerplate mehr

Die ikonische Signatur public static void main(String[] args) gehört der Vergangenheit an, wenn es um einfache Programme geht. Es ist nun möglich, eine Instanzmethode void main() als Einstiegspunkt zu definieren. In Kombination mit unbenannten Klassen kann ein lauffähiges "Hallo Welt"-Programm in einer einzigen Zeile stehen, ohne explizite Klassendefinition.

// Vollständige und lauffähige .java-Datei
void main() {
    println("Hallo Welt!"); // Verwendet die neue, automatisch verfügbare println-Methode
}

Diese .java-Datei kann direkt mit dem java-Befehl ausgeführt werden (java HelloWorld.java), ohne vorherige manuelle Kompilierung mit javac. Die JVM führt eine In-Memory-Kompilierung durch, was ideal für Skripting und schnelle Experimente ist.

Javadoc trifft auf Markdown: Dokumentation neu gedacht

Die Dokumentation von Code wird durch die Unterstützung von Markdown in Javadoc-Kommentaren modernisiert. Anstatt der bisherigen HTML-basierten Syntax können Kommentare, die mit einem dreifachen Schrägstrich (///) beginnen, in CommonMark verfasst werden. Dies vereinfacht die Formatierung erheblich und führt zu sauber gerenderten HTML-Seiten, inklusive korrekter Verlinkung zu anderen Programmelementen.

/// ## Eine Klasse zur Repräsentation eines Punktes.
///
/// Diese Klasse verwendet **Markdown** für eine klare Dokumentation.
///
/// ### Beispiel:
/// ```java
/// var p = new Point(10, 20);
/// ```
///
/// @param x die x-Koordinate
/// @param y die y-Koordinate
public record Point(int x, int y) {}

Fundamentale Verbesserungen: Immutabilität und Null-Sicherheit

Jenseits der syntaktischen Neuerungen wurde mit JEP 513 ein tiefgreifendes Problem bei der Objektkonstruktion adressiert, das die Erstellung wirklich unveränderlicher Objekte erschwerte.

Flexible Konstruktor-Aufrufe: Das Ende der verfrühten Initialisierung

Bisher galt die strikte Regel, dass der Aufruf eines Superklassen-Konstruktors (super()) oder eines anderen Konstruktors derselben Klasse (this()) die allererste Anweisung sein musste. Dies führte zu Problemen, wenn der Superklassen-Konstruktor eine überschriebene Methode aufrief, die auf noch nicht initialisierte Felder der Subklasse zugriff. In solchen Fällen konnte der Zustand eines final-Feldes von null zu seinem endgültigen Wert beobachtet werden, was das Konzept der Immutabilität untergrub.

 

Die neue Flexibilität erlaubt es, Anweisungen vor dem super()-Aufruf auszuführen. Dadurch können Argumente validiert oder vorbereitet werden, bevor der potenziell teure Konstruktor der Superklasse ausgeführt wird.

class ValidatedRange extends Range {
    ValidatedRange(int start, int end) {
        // Validierung findet VOR dem Aufruf von super() statt
        if (start > end) {
            throw new IllegalArgumentException("Start darf nicht größer als Ende sein.");
        }
        
        // Erst nach erfolgreicher Prüfung wird die Superklasse initialisiert
        super(start, end);
    }
}

Um die Sicherheit zu gewährleisten, gelten während dieser "Prologs" vor dem super()-Aufruf strenge Regeln: Der this-Referenz darf nur zur Zuweisung an Instanzfelder verwendet werden. Der Aufruf von Instanzmethoden auf dem sich in Konstruktion befindlichen Objekt ist verboten. Diese Änderungen stärken die Garantien für die Erstellung robuster und wirklich konstanter Objekte in Java.

 

In diesem YouTube-Video von "Oracle Developers" werden viele der neuen Sprachfeatures, die seit Java 21 eingeführt wurden, ausführlich und mit praktischen Beispielen erläutert, was ein tieferes Verständnis der hier beschriebenen Konzepte ermöglicht.