java 22 featuresjava 22 features
HappyCoders Glasses

Java 22 Features
(mit Beispielen)

Sven Woltmann
Sven Woltmann
Aktualisiert: 11. Januar 2024

Java 22 befindet sich seit dem 7. Dezember 2023 in der sogenannten „Rampdown Phase One”, d. h. es werden keine weiteren JDK Enhancement Proposals (JEPs) in das Release aufgenommen. Das Feature-Set steht also fest. Es werden nur noch Bugs gefixt und ggf. kleinere Verbesserungen durchgeführt.

Als Veröffentlichungsdatum ist der 19. März 2024 angepeilt. Die aktuelle Early-Access-Version kannst du hier herunterladen.

Die Highlights von Java 22:

Zudem gehen die in Java 21 als Preview eingeführten Features Structured Concurrency, Scoped Values, String Templates ohne Änderungen in eine zweite Preview-Runde. Und die ebenfalls in Java 21 als Preview eingeführten „Unnamed Classes and Instance Main Methods” wurde 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 z. B. 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");
}Code-Sprache: Java (java)

Hier verwenden wir den Lambda-Paraketer k nicht:

map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);Code-Sprache: Java (java)

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));
}Code-Sprache: Java (java)

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");
}Code-Sprache: Java (java)

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

map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);Code-Sprache: Java (java)

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));
}Code-Sprache: Java (java)

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]);
  }
}Code-Sprache: Java (java)

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!Code-Sprache: Klartext (plaintext)

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 definiert werden und in einer sinnvollen Paketstruktur organisiert werden.

Sobald wir aber weitere Java-Dateien hinzufügen, funktioniert der sogennante „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!%n".formatted(name);
  }
}Code-Sprache: Java (java)

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]));
  }
}Code-Sprache: Java (java)

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

$ java Hello.java World 
Hello.java:5: error: cannot find symbol
    System.out.println(Greetings.greet(args[0]));
                       ^
  symbol:   variable Greetings
  location: class Hello
1 error
error: compilation failedCode-Sprache: Klartext (plaintext)

Die Greetings-Klasse wurde nicht gefunden.

Versuchen wir es noch einmal mit Java 22:

$ java Hello.java World 
Hello World!Code-Sprache: Klartext (plaintext)

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.
  • Wenn die Datei Hello.java in einem Paket liegen würde, beispielsweise in eu.happycoders.java22, dann muss sie auch im entsprechenden Verzeichnis eu.happycoders.java22 liegen, und du musst das java-Kommando im Root-Verzeichis 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 VM-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. Ich denke aber, dass für die meisten Anwendungsfälle das hier beschriebene ausreichend sein sollte.

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 extrem komplizierte, fehleranfällige und langsame Java Native Interface (JNI) ablösen und verspricht im direkten Vergleich 90 % weniger Implementierungsaufwand und die vier- bis fünffache Performance.

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
  }
}Code-Sprache: Java (java)

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 = 13Code-Sprache: Klartext (plaintext)

Weiter will ich in die Details der FFM API an dieser Stelle nicht eingehen. Du findest eine ausführliche Beschreibung der API und all ihrer Komponenten im Hauptartikel über die FFM API.

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));Code-Sprache: Java (java)

Der Code gibt folgendes aus:

Earth, Wind und FireCode-Sprache: Klartext (plaintext)

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

Earth, Wind, and FireCode-Sprache: Klartext (plaintext)

Und die Einstellung Locale.FRANCE führt zu:

Earth, Wind et FireCode-Sprache: Klartext (plaintext)

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:

FULLSHORTNARROW
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:

FULLSHORTNARROW
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 ist im Bug-Tracker unter JDK-8041488 zu finden.

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 sowohl im javac- als auch im java-Kommando explizit über die VM-Optionen --enable-preview --source 22 freischalten.

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 aufrufen 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);
  }
}Code-Sprache: Java (java)

Es ist nicht leicht zu erkennen, was dieser Code tut, um 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);
  }
}Code-Sprache: Java (java)

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 bisher war das nicht möglich. Bisher musste der Aufruf von super(...) das erste Statement in einem Konstruktor sein. Code für die Validierungen von Parametern und die Berechnung von Argumenten für die super(...)-Methode 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 für Statements before super(…).

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, peak, 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, stattdessen eine API zu entwickeln, die es sowohl den JDK-Entwicklern 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);
Code-Sprache: Java (java)

Dieses kleine Beispielprogramm gibt folgendes aus:

[[the, be, two], [of, and, a], [in, that]]
Code-Sprache: Klartext (plaintext)

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);Code-Sprache: Java (java)

Dieses Programm gibt folgendes aus:

[[1, 2, 3], [2, 3, 4], [3, 4, 5]]Code-Sprache: Klartext (plaintext)

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

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-Programmiererinnen und -Programmierer 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, drei 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();
  }
}Code-Sprache: Java (java)

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 ermöglichen.

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));
    . . .
  }
}Code-Sprache: Java (java)

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;
  }
  . . .
}Code-Sprache: Java (java)

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)}";Code-Sprache: Java (java)

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 ohne Änderungen in eine zweite Preview-Runde.

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

Java-Anfängerinnen und -Anfänger 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!");
  }
}Code-Sprache: Java (java)

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:

java 22 implicitly declared classes and instance main methods

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!");
}Code-Sprache: Java (java)

Damit können Anfänger langsam 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
Note: Hello.java uses preview features of Java SE 22.
Note: Recompile with -Xlint:preview for details.
Hello world!Code-Sprache: Klartext (plaintext)

Schauen wir uns die Bestandteile dieses Features – implizit deklarierte Klassen und Instanz-Main-Methoden – einmal genauer an.

Implizit deklarierte Klassen

Für eine Java-Datei ohne explizite Klassendeklaration, also ohne beispielsweise public class HelloWorld erzeugt der Java-Compiler eine implizite Klasse mit einem vom Compiler festgelegten Namen. In der Regel ist das der Name der Datei ohne die .java-Endung.

Wenn du beispielsweise die Datei HelloWorld.java kompilierst, erzeugt der Compiler die Datei HelloWorld.class – und wenn du diese z. B. mit deiner IDE dekompilierst, siehst du, dass auch der Klassenname HelloWorld ist.

Für implizite Klassen gelten die folgenden Besonderheiten:

  • Eine implizite Klasse liegt immer im unbenannten Paket (so wie eine reguläre Klasse ohne package-Deklaration).
  • Eine implizite Klasse ist grundsätzlich final.
  • Eine implizite Klasse kann keine Interfaces implementieren oder von anderen Klassen erben. Ebenso kann keine Klasse von einer impliziten Klasse erben.
  • Auf eine implizite Klasse kann nicht über den vom Compiler festgelegten Namen zugegriffen werden, d. h. andere Klassen können eine implizite Klasse nicht instanziieren, und keine Methoden darauf aufrufen, auch keine statischen.

Eine implizite Klasse kann jedoch Methoden auf sich selbst aufrufen, wie z. B. in folgendem Beispiel:

void main() {
  System.out.println(greeting());
}

String greeting() {
  return "Hello, World!";
}Code-Sprache: Java (java)

Da auf eine implizite Klasse von außerhalb nicht zugegriffen werden kann, muss sie eine Main-Methode enthalten.

Instanz-Main-Methoden

Instanz-Main-Methoden sind nicht-statische Main-Methoden. Folgende Main-Methoden sind zukünftig erlaubt:

  • nicht statische Instanz-Methoden,
  • Methoden mit dem Sichtbarkeitslevel public, protected oder package-private (ohne Modifier),
  • Methoden mit oder ohne String[]-Parameter.

Hier sind ein paar Beispiele:

  • void main()
  • void main(String[] args)
  • public void main()
  • protected static void main(String[] args)

Statische und nicht-statische Methoden mit gleicher Signatur sowie Methoden mit unterschiedlichen Visibility-Modifiern bei gleicher Signatur schließen sich gegenseitig aus und führen zu einem „method is already defined”-Compiler-Fehler.

Es kann allerdings eine Main-Methode mit String[]-Parameter und eine Main-Methode ohne Parameter existieren:

void main(String[] args) {
  . . .
}

protected static void main() {
  . . .
}Code-Sprache: Java (java)

In solch einem Fall wird immer die Methode mit String[]-Parameter, im Beispiel also void main(String[] args), gestartet.

Handelt es sich bei der Main-Methode um eine Instanzmethode, dann wird zunächst eine Instanz der Klasse erzeugt und die Main-Methode auf dieser Instanz aufgerufen.

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 nunmehr über drei Jahren entwickelte neue Vector API geht mit JDK Enhancement Proposal 460 in die siebte Inkubator-Runde.

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

java vector addition
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 ist im Bug-Tracker unter JDK-8309196 zu finden.

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 Funktionalität konnte allerdings bisher über die VM-Option -Djdk.reflect.useDirectMethodHandle=false wieder aktiviert werden.

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

Für diese Änderung gibt es keinen JEP, sie ist im Bug-Tracker unter JDK-8305104 registriert.

Deprecations und Löschungen in sun.misc.Unsafe

Diese Klasse sun.misc.Unsafe stellt Low-Level-Funktionalitäten zur Verfügung, die eigentlich nur von der Java-Kernbibliothek und nicht von anderen Java-Programmen genutzt werden sollen. Das hat allerdings viele Java-Entwickler 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 sind im Bug-Tracker unter JDK-8315938 und JDK-8316160 zu finden.

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 (was 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 bisher Pinning 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 ist im Bug-Tracker unter JDK-8296246 registriert.

Vollständige Liste aller Änderungen in Java 22

In diesem Artikel hast du alle Java 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 Unnamed Classes and Instance Main Methods gehen in eine zweite Preview-Runde.

Diverse sonstige Änderungen runden wie immer das Release ab. Das aktuelle Java 22 Early-Access Release kannst du hier herunterladen.

Auf welches Java-22-Feature freust du dich am meisten? Welches Feature vermisst du? Lasse es mich gerne über die Kommentarfunktion wissen!

Du möchtest über alle neue Java-Features auf dem Laufenden sein? Dann klicke hier, um dich für den kostenlosen HappyCoders-Newsletter anzumelden.