Release von Java 22

Am 19. März 2024 veröffentlichte Oracle mit dem Launch von Java 22 das neueste Update. Java 22 präsentiert uns zwölf Java Enhancement Proposals (JEPs), die ein breites Themenspektrum abdecken. Jean-Claude nimmt in seinem Blogbeitrag drei JEPs unter die Lupe, die für die Entwicklergemeinde von besonderer Bedeutung sein könnten.

Java 22 Release

Für Neugierige: Das Event wurde aufgezeichnet und kann als Launch Stream angeschaut werden. 

Neues von Java

Java 22 enthält zwölf verschiedene JEPs, die eine Fülle von Bereichen abdecken:

  • JVM/ GC
  • High Performance Computing
  • Concurrency
  • Vereinfachungen für den Einstieg in die Entwicklung mit Java
  • Erweiterungen von der Sprache Java selbst
  • Erweiterungen von bestehenden und neuen Core Libraries

The arrival of Java 22 gibt uns einen Überblick zu den genauen Inhalten.  

In diesem Artikel werfen wir nun einen Blick auf drei Sprach- beziehungsweise Library-Erweiterungen.

Java 22: JEP 447
Statements before super(…) (Preview)

Java bietet die Möglichkeit der Klassenvererbung. Dabei muss im Konstruktor der Subklasse als erstes Statement der Konstruktor der Superklasse via super() aufgerufen werden. Es wäre aber manchmal nützlich, vor dem Aufruf von super() noch einen anderen Code auszuführen. Ein möglicher Use Case ist, zuerst eine Validierung der Konstruktorparameter durchzuführen. Wenn die Validierung nicht erfolgreich ist, könnte eine Exception geworfen werden. Das folgende Beispiel zeigt dies:  

Wir starten mit einem public class Point mit x und y Koordinaten als Integer. Die Klasse SwissPoint erweitert Point um eine Validation Rule: für einen SwissPoint muss x zwischen 500 und 1.000 liegen und y zwischen 600 und 800. Falls das nicht der Fall ist, wird eine IllegalArgumentException geworfen.

package jep_447;

public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }
package jep_447;

public class SwissPoint extends Point {

    public SwissPoint(int x, int y) {
        verifyPoint(x, y);
        super(x, y);
    }

    private static void verifyPoint(int x, int y) {
        if ((x < 500 || x > 1000) || (y < 600 || y > 800)) {
            throw new IllegalArgumentException(STR."\{x}|\{y} is not a valid SwissPoint");
        }
    }
}

Dabei ist wichtig zu erwähnen, dass vor dem Aufruf von super() nicht auf Instanzvariablen zugegriffen und auch keine Methoden der Superklasse aufgerufen werden können.

Java 22: JEP 456
Unnamed Variables & Patterns (Stable)

Dieses Feature wurde mit Java 21 als Preview-Feature eingeführt und ist in Java 22 zur finalen Version gereift. Wir schauen uns genauer an, worum es sich dabei handelt.

Es gibt Situationen, in denen uns die Java-Syntax dazu zwingt, eine Variable eines bestimmten Typs zu deklarieren, obwohl diese Variable eigentlich gar nicht verwendet wird. Mit JEP 456 ist es nun möglich, in solchen Fällen als Variablennamen ein «_» zu verwenden. Dadurch wird dem Lesenden deutlich gemacht, dass die Variable gar nicht verwendet wird. Wir betrachten dies anhand von zwei Beispielen näher.

Beispiel: Calculator – try/catch
Wir schreiben eine Klasse Calculator mit einer calculate() Methode, die einen User Input (als String) entgegennimmt und eine «komplizierte» Berechnung durchführt. Für die Berechnung wird ein Integer benötigt, daher muss der Input String geparst werden. Falls das Parsen fehlschlägt, wird die Berechnung mit einem Default Start Wert gemacht.

package jep_456;

public class Calculator {

    private static final int DEFAULT_VALUE = 1;

    public int calculate(String userInput) {
        int number = parseInput(userInput);
        return complicatedCalculation(number);
    }

    private static int parseInput(String userInput) {
        try {
            return Integer.parseInt(userInput);
        } catch (NumberFormatException _) {
            return DEFAULT_VALUE;
        }
    }


    private int complicatedCalculation(int number) {
        return 23 * 42 + number;
    }
}

Bei Integer.parseInt() müssen wir im catch eine Variable vom Typ NumberFormatException deklarieren, die gar nicht verwendet wird. Daher können wir hier nun «_» als Namen verwenden.

Beispiel: Pub – switch Statement
Wir haben das Pub (repräsentiert durch die Klasse Pub) und Customers. Die Klasse Customer ist abstrakt und hat zwei konkrete Implementationen Adult und Teenager.

package jep_456.pub;

public sealed abstract class Customer permits Adult, Teenager {
    private String name;

    public Customer(String name) { this.name = name; }
    public String name() { return this.name; }
}

public final class Adult extends Customer {
    public Adult(String name) {
        super(name);
    }
}

public final class Teenager extends Customer {
    public Teenager(String name) {
        super(name);
    }
}

Sobald eine Person (Customer) das Pub betritt, wird diese bedient. Abhängig davon, ob es sich um einen Erwachsenen oder einen Teenager handelt, erhält die Person entweder ein Bier oder eine Cola serviert.

Für die Implementierung nutzen wir das neue switch-Statement. Dabei fällt auf, dass wir in den Switch-Cases Variablen für konkreten Customer-Typen deklarieren müssen. Diese werden jedoch nicht verwendet. Daher kann auch hier «_» als Variablenname verwendet werden.

package jep_456.pub;

public class Pub {
    public void makesOrder(Customer customer) {
        switch (customer) {
            case Adult _ -> serveBier(customer);
            case Teenager _ -> serveCoke(customer);
        }
    }

    private void serveBier(Customer customer) {
        System.out.println(STR."1 bier for \{customer.name()}");
    }

    private void serveCoke(Customer customer) {
        System.out.println(STR."1 coke for \{customer.name()}");
    }
}

Java 22: JEP 461
Stream Gatherers (Preview)

Als letztes möchten wir die Stream Gatherers anschauen, die eine Erweiterung des Stream APIs sind. Bei den Streams gibt es drei Gruppen von Operationen:

  • Source Operations, die einen Array oder Collection in einen Stream umwandeln.
  • Intermediate Operations, die einzelne Elemente des Streams transformieren. Dazu gehören filter, map etc.
  • Terminal Operations, die einen Wert produzieren oder einen Nebeneffekt («side effect») haben.

Mit den Stream Gatherers wird das Stream API so erweitert, dass einfach Custom Intermediate Operations geschrieben werden können. Zusätzlich gibt es bereits im JDK implementierte Operationen. Als Beispiel schauen wir windowFixed() näher an. 

Wir möchten zunächst einen String mit Integer Werten parsen, diesen in Pairs von zwei Werten aufsplitten und daraufhin von jedem Pair nur den grösseren Wert nehmen. Am Ende sollen die Integer Werte in einer Liste zurückgeben werden. 

Setup

Wir starten mit dem Setup Code, der die Datenstruktur Pair definiert und zwei Hilfsmethoden:

  • parseIntegerString: wandelt einen Input String in eine Liste von Integer Werten.
  • extractMaxValueOfPair: extrahiert aus einer Liste von Pair Objekten immer das grössere Element und gibt diese als Liste zurück.
package jep_461;

public record Pair(int a, int b) {
}

public class PairHelper {

    public static List<Integer> parseIntegerString(String valueString) {
        return Stream.of(valueString.split(","))
                .mapToInt(n -> Integer.valueOf(n))
                .boxed()
                .toList();
    }

    public static List<Integer> extractMaxValueOfPair(List<Pair> pairs) {
        return pairs.stream()
                .map(pair -> pair.a() >= pair.b() ? pair.a() : pair.b())
                .collect(Collectors.toList());
    }
}

Version 1: «Pre Java 22» Implementation

Wir starten mit der Implementation mit «Pre Java 22». Wir teilen das Vorgehen in drei Schritte auf:

  • Parsing des Input Strings in eine Integer Liste (via PairHelper).
  • Konvertierung der Integer Liste in eine Liste von Pairs. Dazu habe ich im Netz einen Ansatz gefunden, wie dies mit Streams implementiert werden könnte. Damit möchte ich aufzeigen, wie kompliziert dies werden kann.
  • Extraktion des grösseren Wertes von jedem Pair und die Rückgabe als Integer Liste (via PairHelper).
public List<Integer> maxValueOfPairList_beforeJava22(String valueString) {
        List<Integer> valesAsIntegers = parseIntegerString(valueString);
        List<Pair> pairs = convertToPairs_beforeJava22(valesAsIntegers);
        return extractMaxValueOfPair(pairs);
    }

    // source: https://stackoverflow.com/questions/27583623/is-there-an-elegant-way-to-process-a-stream-in-chunks
    private List<Pair> convertToPairs_beforeJava22(List<Integer> values) {
        AtomicInteger index = new AtomicInteger(0);
        return values.stream()
                .collect(Collectors.groupingBy(x -> index.getAndIncrement() / 2))
                .entrySet().stream()
                .sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue)
                .map(intList -> new Pair(intList.get(0), intList.get(1)))
                .toList();

Version 2: Java 22 Implementation

Mit der mit Java 22 mitgelieferten Stream Gatherers Implementation windowFixed() geht dies viel eleganter. Der erste und dritte Schritt bleiben bestehen, nur der mittlere Schritt convertToPairs() wird ausgewechselt.

  • Wir erstellen einen Stream von Integer Werten.
  • Über den Stream kann man mit windowFixed(windowSize) in einer vorgegeben windowSize «iterieren»  (hier sind es zwei). Dabei wird der Input Stream<Integer> umgewandelt in einen Stream<List<Integer>>, wobei die Liste immer die Grösse zwei hat (windowSize).
  • Mittels map() kann man auf die beiden Elemente zugreifen und jeweils ein Pair erstellen.

Die Implementation sieht schon viel einfacher aus als bei der «Pre Java 22» Version.

public List<Integer> maxValueOfPairList_withJava22(String valueString) {
        List<Integer> valesAsIntegers = parseIntegerString(valueString);
        List<Pair> pairs = convertToPairs_withJava22(valesAsIntegers);
        return extractMaxValueOfPair(pairs);
    }


    private List<Pair> convertToPairs_withJava22(List<Integer> values) {
        return values.stream()
                .gather(Gatherers.windowFixed(2))
                .map(intList -> new Pair(intList.get(0), intList.get(1)))
                .toList();
    }
}

Fazit

Java 22 umfasst zwölf JEPs, die eine grosse Bandbreite von Themen abdecken. Wir haben uns nun drei JEPs angeschaut, die für die meisten Entwickler*innen relevant sein könnten.

Statements before super(…) und Unnamed Variables & Patterns sind kleinere Erweiterungen, die den Entwicklungsalltag etwas erleichtern können. Mit den Stream Gatherers kommt eine Erweiterung des Stream APIs, die die Möglichkeiten der aktuellen Streams deutlich ausbaut. Es bleibt spannend zu sehen, was auf Basis der Gatherers in Zukunft alles entstehen wird.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert