Zum Inhalt springen

Java 22 Features (mit Beispielen)

Eine 22 aus gerösteten Kaffeebohnen auf einem hellen Eichenbrett vor einer olivgrünen Wand, daneben ein offener Koffer, dessen Schaumstoffeinlage passgenaue Aussparungen für Tasse, Untertasse, Tamper und ein Bohnenglas hat, rechts ein kleiner Olivenbaum im Terrakottatopf

Java 22 ist am 19. März 2024 erschienen und steht im OpenJDK-Archiv zum Download bereit.

Die Highlights von Java 22:

Zudem gehen die in Java 21 als Preview eingeführten Features Structured Concurrency, Scoped Values und String Templates in eine zweite Preview-Runde – die ersten beiden ohne Änderungen, String Templates mit einer kleinen technischen Änderung. Und die ebenfalls in Java 21 als Preview eingeführten „Unnamed Classes and Instance Main Methods“ wurden noch einmal überarbeitet und umbenannt in Implicitly Declared Classes and Instance Main Methods.

Für alle JEPs und sonstigen Änderungen verwende ich wie immer die originalen, englischen Bezeichnungen.

Unnamed Variables & Patterns – JEP 456

Oft müssen wir Variablen definieren, die wir gar nicht benötigen. Gängige Beispiele sind Exceptions, Lambda-Parameter und Patterns.

Im folgenden Beispiel verwenden wir die Exception-Variable e nicht:

try {
  int number = Integer.parseInt(string);
} catch (NumberFormatException e) {
  System.err.println("Not a number");
}

Hier verwenden wir den Lambda-Parameter k nicht:

map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

Und im folgenden Record-Pattern verwenden wir die Pattern-Variable position2 nicht:

if (object instanceof Path(Position(int x1, int y1), Position position2)) {
  System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}

Alle drei Code-Beispiele können in Java 22 mit unbenannten Variablen und unbenannten Patterns ausdrucksstärker formuliert werden, indem die Namen der Variablen oder das komplette Pattern durch den Unterstrich (_) ersetzt werden:

Die Exception-Variable e können wir durch _ ersetzen:

try {
  int number = Integer.parseInt(string);
} catch (NumberFormatException _) {
  System.err.println("Not a number");
}

Den Lambda-Parameter k können wir durch _ ersetzen:

map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);

Und das komplette Teil-Pattern Position position2 können wir durch _ ersetzen:

if (object instanceof Path(Position(int x1, int y1), _)) {
  System.out.printf("object is a path starting at x = %d, y = %d%n", x1, y1);
}

Unnamed Variables & Patterns wurde in Java 21 als Preview-Feature unter dem Namen „Unnamed Patterns and Variables“ veröffentlicht und wird in Java 22 durch JDK Enhancement Proposal 456 ohne Änderungen finalisiert.

Eine ausführlichere Beschreibung findest du im Hauptartikel über unbenannte Variablen und Patterns.

Launch Multi-File Source-Code Programs – JEP 458

Seit Java 11 können wir Java-Programme, die aus nur einer Datei bestehen, direkt ausführen, ohne sie vorher kompilieren zu müssen.

Speichere z. B. einmal den folgenden Java-Code in der Datei Hello.java:

public class Hello {
  public static void main(String[] args) {
    System.out.printf("Hello %s!%n", args[0]);
  }
}

Du brauchst dieses Programm nicht, wie vor Java 11, erst mit javac zu kompilieren, sondern kannst es direkt ausführen:

$ java Hello.java World
Hello World!

Wir können in der Datei Hello.java auch mehrere Klassen definieren, doch wenn unser Programm wächst, wird das schnell unübersichtlich; die anderen Klassen sollten in eigenen Dateien stehen und in einer sinnvollen Paketstruktur organisiert werden.

Sobald wir aber weitere Java-Dateien hinzufügen, funktioniert der sogenannte „Launch Single-File Source-Code“-Mechanismus aus Java 11 nicht mehr.

Lass uns einmal die Berechnung der Begrüßung in eine andere Klasse auslagern und diese in der Datei Greetings.java speichern:

public class Greetings {
  public static String greet(String name) {
    return "Hello %s!".formatted(name);
  }
}

Die Hello-Klasse lassen wir wie folgt die Greetings-Klasse verwenden:

public class Hello {
  public static void main(String[] args) {
    System.out.println(Greetings.greet(args[0]));
  }
}

Wenn wir das Programm nun direkt mit java starten wollen, passierte – jedenfalls bis Java 21 – Folgendes:

$ java Hello.java World
Hello.java:3: error: cannot find symbol
    System.out.println(Greetings.greet(args[0]));
                       ^
  symbol:   variable Greetings
  location: class Hello
1 error
error: compilation failed

Die Greetings-Klasse wurde nicht gefunden.

Versuchen wir es noch einmal mit Java 22:

$ java Hello.java World
Hello World!

Aus dem „Launch Single-File Source-Code“-Feature wurde in Java 22 ein „Launch Multi-File Source-Code Programs“-Feature. Der Code darf nun in beliebig vielen Java-Dateien strukturiert sein.

Folgende Besonderheiten musst du dabei beachten:

  • Würde die Greetings-Klasse nicht nur in der Datei Greetings.java, sondern ebenfalls in der Datei Hello.java definiert sein, dann würde das java-Kommando die in der Datei Hello.java definierte Klasse verwenden. Es würde die Datei Greetings.java gar nicht erst suchen und somit auch keine Fehlermeldung ausgeben, dass die Klasse doppelt definiert ist.
  • Liegt die Datei Hello.java in einem Paket, beispielsweise in eu.happycoders.java22, dann muss sie auch im entsprechenden Verzeichnis eu/happycoders/java22 liegen, und du musst das java-Kommando im Root-Verzeichnis wie folgt aufrufen:
    java eu/happycoders/java22/Hello.java World
  • Möchtest du Code aus JAR-Dateien verwenden, kannst du diese z. B. in ein Verzeichnis libs legen und das java-Kommando dann mit der Option --class-path 'libs/*' aufrufen.

Definiert ist dieses Feature in JDK Enhancement Proposal 458. Dort wird auch erklärt, wie das Feature bei der Verwendung von Modulen funktioniert und wie einige Sonderfälle, die theoretisch eintreten könnten, behandelt werden. Für die meisten Anwendungsfälle reicht das hier Beschriebene.

Foreign Function & Memory API – JEP 454

Nach vielen Jahren der Entwicklung in Project Panama und nach insgesamt acht Incubator- und Preview-Versionen wird die Foreign Function & Memory API in Java 22 durch JDK Enhancement Proposal 454 endlich finalisiert.

Die Foreign Function & Memory API (oder kurz: FFM API) ermöglicht es, aus Java heraus auf Code außerhalb der JVM (z. B. auf Funktionen in Bibliotheken, die in anderen Programmiersprachen implementiert wurden) sowie auf nativen Speicher (also Speicher, der nicht von der JVM im Heap verwaltet wird) zuzugreifen.

Die FFM API soll das umständliche und fehleranfällige Java Native Interface (JNI) ablösen. JEP 454 nennt dafür unter anderem zwei Ziele: eine knappe, lesbare, reine Java-API anstelle von JNI – und einen Overhead, der dem von JNI vergleichbar oder geringer ist.

Ich zeige dir hier an einem einfachen Beispiel, wie die API funktioniert – eine umfangreichere Einführung in das Thema findest du im Hauptartikel über die Foreign Function & Memory API.

Der folgende Code ruft die strlen()-Funktion der Standard-C-Library auf, um die Länge des Strings „Happy Coding!“ zu bestimmen:

public class FFMTest22 {
  public static void main(String[] args) throws Throwable {
    // 1. Get a lookup object for commonly used libraries
    SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();

    // 2. Get a handle to the "strlen" function in the C standard library
    MethodHandle strlen =
        Linker.nativeLinker()
            .downcallHandle(
                stdlib.find("strlen").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

    // 3. Get a confined memory area (one that we can close explicitly)
    try (Arena offHeap = Arena.ofConfined()) {

      // 4. Convert the Java String to a C string and store it in off-heap memory
      MemorySegment str = offHeap.allocateFrom("Happy Coding!");

      // 5. Invoke the foreign function
      long len = (long) strlen.invoke(str);
      System.out.println("len = " + len);
    }
    // 6. Off-heap memory is deallocated at end of try-with-resources
  }
}

Die einzelnen Schritte der Vorgehensweise sind durch Kommentare direkt im Quellcode beschrieben.

Der Code unterscheidet sich von der im Java-21-Artikel vorgestellten Version in nur einem Detail: Die Arena.allocateFrom(...)-Methode hieß zuvor allocateUtf8String(...).

Du kannst das Programm mit Java 22 wie folgt starten:

$ java --enable-native-access=ALL-UNNAMED FFMTest22.java
len = 13

Ohne --enable-native-access=ALL-UNNAMED läuft das Programm ebenfalls, gibt aber vor dem Ergebnis eine Warnung aus: Linker.downcallHandle() ist eine sogenannte „restricted method“, und solche Aufrufe sollen zukünftig ohne diese Option blockiert werden.

Weiter will ich an dieser Stelle nicht in die Details der FFM API einsteigen; die API und all ihre Komponenten beschreibt der oben verlinkte Hauptartikel.

Locale-Dependent List Patterns

Mit der neuen Klasse ListFormat lassen sich Listen als Aufzählung formatieren, so wie wir sie in Fließtext formulieren würden.

Hier ist ein Beispiel:

List<String> list = List.of("Earth", "Wind", "Fire");
ListFormat formatter =
    ListFormat.getInstance(Locale.GERMAN, Type.STANDARD, Style.FULL);
System.out.println(formatter.format(list));

Der Code gibt Folgendes aus:

Earth, Wind und Fire

Wenn wir den locale-Parameter auf Locale.US ändern, kommt Folgendes heraus:

Earth, Wind, and Fire

Und die Einstellung Locale.FRANCE führt zu:

Earth, Wind et Fire

Neben locale hat die ListFormat.getInstance(...)-Methode noch zwei weitere Parameter:

type – der Typ der Aufzählung – hier gibt es drei Varianten:

  • STANDARD für eine Auflistung mit „und“,
  • OR für eine Auflistung mit „oder“,
  • UNIT für eine Auflistung von Einheiten, entspricht je nach Locale einer Auflistung mit „und“ oder einer mit nur Kommas.

style – der Stil der Aufzählung – auch hier gibt es drei Varianten:

  • FULL – die Bindewörter wie „und“ und „oder“ werden ausgeschrieben.
  • SHORT – die Bindewörter werden je nach Locale ausgeschrieben oder abgekürzt.
  • NARROW – die Bindewörter werden je nach Locale ausgeschrieben oder weggelassen, ggf. werden auch Kommas weggelassen.

In der folgenden Tabelle siehst du alle Kombinationen für Locale.GERMANY:

Type \ StyleFULLSHORTNARROW
STANDARDEarth, Wind und FireEarth, Wind und FireEarth, Wind und Fire
OREarth, Wind oder FireEarth, Wind oder FireEarth, Wind oder Fire
UNITEarth, Wind und FireEarth, Wind und FireEarth, Wind und Fire

Wie du siehst, gibt es im Deutschen keine Unterschiede zwischen den Typen STANDARD und UNIT, und der Stil spielt überhaupt keine Rolle.

Schauen wir uns einmal das Locale.US-Format an:

Type \ StyleFULLSHORTNARROW
STANDARDEarth, Wind, and FireEarth, Wind, & FireEarth, Wind, Fire
OREarth, Wind, or FireEarth, Wind, or FireEarth, Wind, or Fire
UNITEarth, Wind, FireEarth, Wind, FireEarth Wind Fire

Hier lassen sich hingegen mehr Unterschiede ausmachen: Beim Typ UNIT wird vor dem letzten Element kein Verbindungswort eingefügt, und das „and“ wird beim Stil SHORT zu „&“ und fällt beim Stil NARROW komplett weg.

Die parameterlose Methode ListFormat.getInstance() liefert ein Listenformat für die Standard-Locale, den Typ STANDARD und den Stil FULL.

Für diese Änderung gibt es keinen JEP; sie wird im Bug-Tracker unter JDK-8041488 aufgeführt.

Neue Preview-Features in Java 22

Java 22 bringt uns drei neue Features im Preview-Stadium. Da sich Preview-Features noch ändern können, solltest du sie nicht in Produktivcode einsetzen. Du musst sie explizit freischalten: beim Kompilieren mit javac --enable-preview --source 22, beim Start mit java --enable-preview. Startest du eine .java-Datei direkt, ohne sie vorher zu kompilieren, brauchst du auch bei java beide Optionen.

Statements before super(…) (Preview) – JEP 447

Hast du dich auch schon mal geärgert, dass du vor dem Aufruf eines Super-Konstruktors mit super(...) oder vor dem Aufruf eines alternativen Konstruktors mit this(...) keinen anderen Code ausführen darfst?

Musstest du auch schon mal ein Ungetüm wie das folgende schreiben, nur um das Argument für einen Super-Konstruktor zu berechnen oder zu validieren?

public class Square extends Rectangle {
  public Square(Color color, int area) {
    this(color, Math.sqrt(validateArea(area)));
  }

  private static double validateArea(int area) {
    if (area < 0) throw new IllegalArgumentException();
    return area;
  }

  private Square(Color color, double sideLength) {
    super(color, sideLength, sideLength);
  }
}

Es ist nicht leicht zu erkennen, was dieser Code tut und warum er das auf so komplizierte Weise macht.

Wäre es nicht viel schöner, man könnte einfach Folgendes schreiben?

public class Square extends Rectangle {
  public Square(Color color, int area) {
    if (area < 0) throw new IllegalArgumentException();
    double sideLength = Math.sqrt(area);
    super(color, sideLength, sideLength);
  }
}

Hier ist sofort ersichtlich, was der Konstruktor tut (selbst ohne die Rectangle-Elternklasse zu kennen):

  1. Er validiert, dass area nicht negativ ist.
  2. Er berechnet die Seitenlänge des Quadrats als Wurzel der Fläche.
  3. Er ruft den Super-Konstruktor von Rectangle auf und übergibt als Breite und Höhe die Seitenlänge des Quadrats.

Doch bis Java 21 war das nicht möglich: Der Aufruf von super(...) musste das erste Statement in einem Konstruktor sein. Code für die Validierungen von Parametern und die Berechnung von Argumenten für den Aufruf von super(...) musste kompliziert in separate Methoden oder alternative Konstruktoren ausgelagert werden, wie im ersten Code-Beispiel.

Mit Java 22 (mit aktivierten Preview-Features) kannst du den Code nun so schreiben wie im zweiten Beispiel: mit Validierungs- und Berechnungslogik vor dem Aufruf von super(...)!

Das Preview-Feature „Statements before super(…)“ wird in JDK Enhancement Proposal 447 definiert.

Eine tiefergehende Betrachtung und einige Besonderheiten, die du beim Schreiben von Code vor super(...) oder this(...) beachten musst, findest du im Hauptartikel über Flexible Constructor Bodies (wie das Feature ab Java 23 heißt).

Stream Gatherers (Preview) – JEP 461

Seit Jahren stößt die begrenzte Zahl an intermediären Stream-Operationen der Java-Stream-API auf Kritik. Neben den bestehenden Operationen filter(), map(), flatMap(), mapMulti(), distinct(), sorted(), peek(), limit(), skip(), takeWhile() und dropWhile() wünscht sich die Java-Community Methoden wie window() und fold() und zahlreiche mehr.

Doch anstatt all diese Methoden ins JDK zu integrieren, entschieden sich die JDK-Entwickler:innen, eine API zu entwickeln, die es sowohl den JDK-Entwickler:innen als auch der Java-Community ermöglicht, beliebige intermediäre Stream-Operationen zu schreiben.

Die neue API heißt „Stream Gatherers“ und wird durch JDK Enhancement Proposal 461 in Java 22 erstmals als Preview-Feature veröffentlicht.

Zusammen mit der API wird eine ganze Reihe vordefinierter Gatherer mitgeliefert, so zum Beispiel die gewünschten Window- und Fold-Operationen.

Mit der „Fixed Window“-Operation z. B. kannst du Stream-Elemente in Listen vorgegebener Größen gruppieren:

List<String> words = List.of("the", "be", "two", "of", "and", "a", "in", "that");

List<List<String>> fixedWindows = words.stream()
    .gather(Gatherers.windowFixed(3))
    .toList();

System.out.println(fixedWindows);

Dieses kleine Beispielprogramm gibt Folgendes aus:

[[the, be, two], [of, and, a], [in, that]]

Mit der „Sliding Window“-Operation kannst du Stream-Elemente ebenfalls gruppieren, allerdings überlappen die erzeugten Listen und sind um jeweils ein Element verschoben:

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

List<List<Integer>> slidingWindows = numbers.stream()
    .gather(Gatherers.windowSliding(3))
    .toList();

System.out.println(slidingWindows);

Dieses Programm gibt Folgendes aus:

[[1, 2, 3], [2, 3, 4], [3, 4, 5]]

Welche weiteren vordefinierten Stream-Gatherer Java 22 bereithält und wie du solch einen Gatherer selbst implementierst, erfährst du im Hauptartikel über Stream Gatherers.

Class-File API (Preview) – JEP 457

Die Java Class-File API ist eine Schnittstelle zum Lesen und Schreiben von .class-Dateien, also von kompiliertem Java-Bytecode. Die neue API soll das im JDK intensiv genutzte Bytecode-Manipulations-Framework ASM ablösen.

Der Grund für die Entwicklung einer eigenen API liegt darin, dass eine Library benötigt wird, die zum einen mit dem sechsmonatigen Veröffentlichungszyklus des JDK Schritt halten kann und zum anderen nicht immer eine Version hinterherhinkt. Denn erst wenn ein JDK veröffentlicht ist, kann ASM auf diese Version angepasst werden – die neue ASM-Version kann dann aber wiederum erst in der nächsten JDK-Version eingesetzt werden.

Da die meisten Java-Programmierer:innen wahrscheinlich nie direkt mit der Class-File API in Berührung kommen werden, werde ich sie an dieser Stelle nicht detailliert beschreiben.

Sollte die Class-File API für dich von Interesse sein, findest du alle Einzelheiten in JDK Enhancement Proposal 457.

Wiedervorgelegte Preview- und Incubator-Features

Fünf Preview- und Incubator-Features werden in Java 22 wiedervorgelegt, zwei davon ohne Änderungen gegenüber Java 21:

Structured Concurrency (Second Preview) – JEP 462

Structured Concurrency ermöglicht eine einfache Koordination von nebenläufigen Aufgaben und führt mit StructuredTaskScope eine Kontrollstruktur ein, die den Anfang und das Ende nebenläufiger Tasks klar definiert, eine saubere Fehlerbehandlung ermöglicht und Teilaufgaben, deren Ergebnisse nicht mehr benötigt werden, geordnet abbrechen kann.

So lässt sich z. B. sehr leicht eine race()-Methode implementieren, die zwei Tasks startet, das Ergebnis des zuerst beendeten Tasks zurückliefert und den zweiten Task abbricht:

public static <R> R race(Callable<R> task1, Callable<R> task2)
    throws InterruptedException, ExecutionException {
  try (var scope = new StructuredTaskScope.ShutdownOnSuccess<R>()) {
    scope.fork(task1);
    scope.fork(task2);
    scope.join();
    return scope.result();
  }
}

Structured Concurrency wurde in Java 21 als Preview-Feature vorgestellt. Eine ausführlichere Behandlung und zahlreiche weitere Beispiele findest du im Hauptartikel über Structured Concurrency.

Durch JDK Enhancement Proposal 462 geht dieses Feature in Java 22 ohne Änderungen in eine zweite Preview-Phase, um der Java-Community weitere Gelegenheit zum Testen und zur Einreichung von Feedback zu geben.

Scoped Values (Second Preview) – JEP 464

Scoped Values erlauben es, einen oder mehrere Werte an eine oder mehrere Methoden zu übergeben, ohne sie als explizite Parameter zu definieren und von einer Methode zur nächsten weiterreichen zu müssen.

Das folgende Beispiel zeigt, wie ein Webserver den eingeloggten User als Scoped Value definiert und in dessen Scope den Request weiterverarbeitet:

public class Server {
  public final static ScopedValue<User> LOGGED_IN_USER = ScopedValue.newInstance();
  . . .
  private void serve(Request request) {
    . . .
    User loggedInUser = authenticateUser(request);
    ScopedValue.where(LOGGED_IN_USER, loggedInUser)
               .run(() -> restAdapter.processRequest(request));
    . . .
  }
}

Nehmen wir an, der durch den Server aufgerufene REST-Adapter ruft einen Service auf und dieser Service wiederum ein Repository. In diesem Repository könnten wir dann z. B. wie folgt auf den eingeloggten User zugreifen:

public class Repository {
  . . .
  public Data getData(UUID id) {
    Data data = findById(id);
    User loggedInUser = Server.LOGGED_IN_USER.get();
    if (loggedInUser.isAdmin()) {
      enrichDataWithAdminInfos(data);
    }
    return data;
  }
  . . .
}

Scoped Values wurden gemeinsam mit Structured Concurrency in Java 21 als Preview-Feature eingeführt. Eine ausführliche Einführung und einen Vergleich mit ThreadLocal-Variablen findest du im Hauptartikel über Scoped Values.

Auch Scoped Values gehen in Java 22 ohne Änderungen in eine zweite Preview-Runde, spezifiziert durch JDK Enhancement Proposal 464.

String Templates (Second Preview) – JEP 459

Mit String Templates können Strings zur Laufzeit durch sogenannte String-Interpolation anhand von Variablen und berechneten Werten zusammengesetzt werden:

int a = ...;
int b = ...;

String interpolated = STR."\{a} times \{b} = \{Math.multiplyExact(a, b)}";

Zur Laufzeit werden hier folgende Ersetzungen vorgenommen:

  • \{a} wird durch den Wert der Variablen a ersetzt.
  • \{b} wird durch den Wert der Variablen b ersetzt.
  • \{Math.multiplyExact(a, b)} wird durch das Ergebnis des Aufrufes der Methode Math.multiplyExact(a, b) ersetzt.

String Templates wurden in Java 21 als Preview-Feature eingeführt. Eine ausführlichere Beschreibung findest du im Hauptartikel über String Templates.

Und auch String Templates gehen – durch JDK Enhancement Proposal 459 spezifiziert – als drittes Preview-Feature in Java 22 in eine zweite Preview-Runde. Geändert hat sich nur ein technisches Detail: der Typ von Template-Ausdrücken.

Implicitly Declared Classes and Instance Main Methods (Second Preview) – JEP 463

Java-Anfänger:innen beginnen oft mit sehr einfachen Programmen, wie z. B. dem folgenden:

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

Für Java-Neulinge kann dieses Beispiel trotz seiner Einfachheit extrem verwirrend sein, denn sie sind in der Regel noch nicht vertraut mit Konzepten wie Sichtbarkeitsmodifikatoren, Klassenstrukturen und statischen Methoden. Der ungenutzte args-Parameter und das schwergewichtige System.out.println(...) tun ihr Übriges.

Wäre es nicht viel einfacher, wenn man all diese überflüssigen Elemente entfernen könnte? Zum Beispiel so:

Hello-World-Programm, in dem die Klassendeklaration, „public static“ und „String[] args“ durchgestrichen sind

JDK Enhancement Proposal 463 macht dies möglich. Der folgende, nach der Löschaktion verbleibende Code ist somit ein gültiges und vollständiges Java-Programm:

void main() {
  System.out.println("Hello world!");
}

Damit können Anfänger:innen langsamer als bisher an die Sprache herangeführt werden. Konzepte, die für größere Programme relevant werden, wie Klassen, die Unterscheidung in statische und Instanzmethoden, Sichtbarkeitsmodifikatoren wie public, protected und private sowie grobgranulare Strukturen wie Pakete und Module können so nach und nach gelehrt werden.

Beachte, dass dieses Feature in Java 22 noch im Preview-Modus ist. Wenn du das Programm unter HelloWorld.java speicherst, kannst du es wie folgt starten:

$ java --enable-preview --source 22 HelloWorld.java
Hello world!

Mehr Details über die Bestandteile dieses Features – einfache Quelldateien, implizit deklarierte Klassen und Instanz-Main-Methoden – kannst du im Abschnitt Compact Source Files and Instance Main Methods (so heißt das Feature seit Java 25, in dem es finalisiert wurde) des Artikels über die Java-main-Methode nachlesen.

Rückblick

Das Feature wurde in Java 21 bereits unter dem Namen Unnamed Classes and Instance Main Methods vorgestellt. Das Konzept der unbenannten Klasse wurde für Java 22 in das deutlich simplere Konzept der implizit deklarierten Klasse abgeändert, und das Launch-Protokoll der Main-Methode wurde vereinfacht.

Vector API (Seventh Incubator) – JEP 460

Die seit Java 16 entwickelte Vector API geht mit JDK Enhancement Proposal 460 in die siebte Incubator-Runde.

Die Vector API ermöglicht es, Vektor-Operationen durchzuführen, wie z. B. die folgende Vektor-Addition:

Vektoraddition: (7, 13, 8, 20) + (16, 3, 15, 9) = (23, 16, 23, 29)
Beispiel einer Vektoraddition

Das Besondere dabei ist, dass die JVM diese Berechnungen auf möglichst effiziente Art auf die Vektor-Operationen moderner CPUs abbildet, so dass solche Berechnungen (bis zu einer bestimmten Vektorgröße) in einem einzigen CPU-Zyklus ausgeführt werden können.

Da sich das Feature noch immer im Incubator-Stadium befindet, also wesentliche Änderungen nicht ausgeschlossen sind, werde ich in diesem Artikel noch nicht weiter auf die Details eingehen. Ich werde die neue API detailliert vorstellen, sobald sie das Preview-Stadium erreicht.

Deprecations und Löschungen

Auch in Java 22 wurden wieder einige Funktionen als „deprecated“ markiert bzw. zuvor als „deprecated for removal“ markierte Funktionen aus dem JDK entfernt.

Thread.countStackFrames has been removed

Die Methode Thread.countStackFrames() wurde bereits in Java 1.2 im Jahr 1998 als „deprecated“ markiert, in Java 9 wurde sie als „deprecated for removal“ markiert, und seit Java 14 wirft sie eine UnsupportedOperationException.

In Java 22 wird die Methode nun entfernt.

Zur Untersuchung des aktuellen Stacks wurde als Alternative bereits in Java 9 die StackWalker-API eingeführt.

Für diese Änderung gibt es keinen JEP; sie wird im Bug-Tracker unter JDK-8309196 aufgeführt.

The Old Core Reflection Implementation Has Been Removed

In Java 18 wurde der Core-Reflection-Mechanismus auf Basis von Method Handles neu implementiert. Die alte Implementierung konnte allerdings bis Java 21 über die Option -Djdk.reflect.useDirectMethodHandle=false wieder aktiviert werden.

In Java 22 wird die alte Funktionalität komplett entfernt, und die o. g. Option wird ignoriert.

Für diese Änderung gibt es keinen JEP; sie wird im Bug-Tracker unter JDK-8305104 aufgeführt.

Deprecations und Löschungen in sun.misc.Unsafe

Die Klasse sun.misc.Unsafe stellt Low-Level-Funktionalitäten zur Verfügung, die nur von der Java-Kernbibliothek und nicht von anderen Java-Programmen genutzt werden sollen. Das hat allerdings viele Java-Entwickler:innen nicht davon abgehalten, Unsafe dennoch zu benutzen.

Für viele Methoden in Unsafe wurden im Laufe der Zeit öffentliche APIs im JDK zur Verfügung gestellt. Diese Methoden werden zunächst als „deprecated“ markiert, dann als „deprecated for removal“, und schließlich werden sie komplett entfernt.

In Java 22 sind folgende Methoden von der Aufräumaktion betroffen:

  • Unsafe.park() und unpark() wurden in Java 5 durch java.util.concurrent.LockSupport.park() und unpark() ersetzt und werden in Java 22 als „deprecated for removal“ markiert.
  • Unsafe.getLoadAverage() wurde in Java 6 durch java.lang.management.OperatingSystemMXBean.getSystemLoadAverage() ersetzt und wird nun ebenfalls als „deprecated for removal“ markiert.
  • Unsafe.loadFence(), storeFence() und fullFence() wurden in Java 9 durch gleichnamige Methoden in java.lang.invoke.VarHandle ersetzt und werden ebenfalls als „deprecated for removal“ markiert.
  • Unsafe.shouldBeInitialized() und ensureClassInitialized() wurden in Java 15 durch java.lang.invoke.MethodHandles.Lookup.ensureInitialized() ersetzt und in derselben Java-Version als „deprecated for removal“ markiert. Die Methoden werden in Java 22 entfernt.

Für diese Änderungen gibt es keinen JEP; sie werden im Bug-Tracker unter JDK-8315938 und JDK-8316160 aufgeführt.

Sonstige Änderungen in Java 22

In diesem Abschnitt findest du einige Änderungen, mit denen du in der täglichen Arbeit mit Java 22 eher nicht in Berührung kommen wirst. Es ist dennoch gut, über diese Änderungen Bescheid zu wissen.

Region Pinning for G1 – JEP 423

Bei der Arbeit mit JNI (das langfristig durch die in Java 22 finalisierte Foreign Function & Memory API abgelöst werden soll) verwendet man ggf. Methoden, die Zeiger auf die Speicheradresse eines Java-Objekts zurückgeben und dann wieder freigeben. Ein Beispiel sind die Funktionen GetStringCritical und ReleaseStringCritical.

Solange solch ein Zeiger noch nicht durch die entsprechende Release-Methode wieder freigegeben wurde, darf der Garbage Collector das Objekt nicht im Speicher verschieben, da dies den Zeiger ungültig machen würde.

Wenn ein Garbage Collector sogenanntes „Pinning“ unterstützt, kann die JVM ihn anweisen, solch ein Objekt anzupinnen, was bedeutet, dass der Garbage Collector es nicht verschieben darf.

Wenn der Garbage Collector dieses Pinning jedoch nicht unterstützt, bleibt der JVM nichts anderes übrig, als die Garbage Collection komplett zu pausieren, sobald irgendeine Get*Critical-Methode aufgerufen wurde und sie erst dann wieder zu aktivieren, wenn alle entsprechenden Release*Critical-Methoden aufgerufen wurden. Je nach Verhalten einer Anwendung kann das drastische Konsequenzen auf den Speicherverbrauch und die Performance haben.

Der G1-Garbage-Collector hat Pinning bis Java 21 nicht unterstützt. Mit JDK Enhancement Proposal 423 wird er um die Pinning-Funktionalität erweitert, d. h. ab Java 22 braucht der Garbage Collector nicht mehr pausiert zu werden.

Support für Unicode 15.1

In Java 22 wird die Unicode-Unterstützung auf Unicode-Version 15.1 angehoben, die den Umfang des Zeichensatzes um 627 hauptsächlich chinesische Symbole auf insgesamt 149.813 Zeichen erhöht.

Relevant ist das für Klassen wie String und Character, die mit den neuen Zeichen umgehen können müssen. Ein Beispiel dafür findest du im Artikel zu Java 11.

Für diese Änderung gibt es keinen JEP; sie wird im Bug-Tracker unter JDK-8296246 aufgeführt.

Make LockingMode a product flag

Die in Java 21 eingeführte VM-Option -XX:LockingMode wird von einer experimentellen zu einer produktiven Option befördert, d. h. sie muss nicht mehr mit -XX:+UnlockExperimentalVMOptions kombiniert werden.

Für diese Änderung gibt es keinen JEP; sie wird im Bug-Tracker unter JDK-8315061 aufgeführt.

Vollständige Liste aller Änderungen in Java 22

In diesem Artikel hast du alle JDK Enhancement Proposals kennengelernt, die in Java 22 umgesetzt wurden, sowie eine Auswahl weiterer Änderungen aus dem Bug-Tracker. Alle weiteren Änderungen findest du in den offiziellen Java 22 Release Notes.

Fazit

Die Fanfaren des Java-21-Launches sind noch zu hören, da wartet bereits Java 22 mit beeindruckenden Features auf:

Unbenannte Variablen und Patterns und Statements before super (Letzteres noch in der Preview-Phase) werden Java-Code in Zukunft noch ausdrucksstärker machen.

Mit Stream Gatherers können wir endlich beliebige intermediäre Stream-Operationen schreiben, so wie wir mit Kollektoren auch schon immer terminale Operationen schreiben konnten.

Mit Launch Multi-File Source-Code Programs können wir Programme, die aus nur einer Datei bestehen, endlich sauber erweitern, ohne auf den Komfort des Startens ohne explizites Kompilieren verzichten zu müssen.

Die Foreign Function & Memory API ist endlich finalisiert und für den produktiven Einsatz bereit.

Structured Concurrency, Scoped Values, String Templates und Implicitly Declared Classes and Instance Main Methods gehen in eine zweite Preview-Runde.

Diverse sonstige Änderungen runden wie immer das Release ab.

Konntest du etwas aus diesem Artikel mitnehmen? Mit einer Bewertung auf meinem ProvenExpert-Profil hilfst du anderen Entwickler:innen einzuschätzen, ob sich das Lesen lohnt – und mir zu verstehen, welche Inhalte dir weiterhelfen.

👉 Bewertung abgeben

Dieses Thema im eigenen Code?

Du hast den Artikel gelesen – im Training arbeitet ihr damit. 2 Tage lang gehen wir die Themen an euren eigenen Projekten durch, statt an konstruierten Beispielen.

Praxisnah, verständlich und direkt auf euren Projektalltag übertragbar. Statt Theorie vermittle ich Prinzipien, die euch helfen, Code langfristig besser, wartbarer und performanter zu schreiben.

Java 25 SchulungAlle Trainings ansehen

Werde ein:e bessere:r Java-Entwickler:in

Mit meinem kostenlosen Newsletter bleibst du vorn. Modernes Java: neue Versionen & Features, Performance und JVM-Insights – 1x im Monat.

Suche