Von Java 11 zu Java 17

Seit einigen Jahren erscheinen halbjährlich neue Java-Versionen. In dieser Versionsflut stechen die LTS-Versionen (Long Term Support) heraus. Zuletzt war das Java 11, aktuell ist es Java 17. Eine gute Gelegenheit also, einen Blick auf die Neuerungen von Java der letzten Jahre zu werfen.

Ich beschränkt mich in diesem Artikel auf die (aus meiner Sicht) wichtigsten Neuerungen. Daneben gibt es eine Menge weiterer neuer Features, die je nach Zielgruppe durchaus interessant sind. Werfen Sie gegebenenfalls einen Blick auf die Links am Ende dieses Artikels!

Oracle JDK wieder uneingeschränkt frei

In den vergangenen Jahren hat Oracle versucht, den JDK-Zugang für die kommerzielle Nutzung kostenpflichtig zu machen. Zwar standen die halbjährlichen Java-Versionen stets zum kostenfreien Download zur Verfügung, aber bei den LTS-Version wollte Oracle Geld für Updates sehen.

Man kann sich natürlich auch ins Knie schießen. Der wenig überraschende Effekt war, dass gefühlt jede größere IT-Firma ein eigenes JDK auf OpenJDK-Basis ins Leben rief. (Der Java-Quellcode ist ja Open Source.) Unter Privatanwendern außerhalb der Linux-Szene waren vor allem die Downloads der Seite https://adoptopenjdk.net/ beliebt. Mittlerweile ist daraus das Projekt Adoptium geworden, an dem unter anderem IBM, Microsoft und Red Hat mitarbeiten.

Wie dem auch sei, seit Java 17 bietet auch Oracle das JDK wieder uneingeschränkt frei an. Natürlich braucht es dazu eine eigene Lizenz, die Oracle No-Fee Terms and Conditions, leicht zu merken als NFTC. Wem fällt das alles nur ein?

Records

Die vermutlich spannendste Neuerung der letzten drei Jahre sind Records. Das neue Schlüsselwort record bietet einen besonders effizienten Weg an, um Klassen zu definieren, deren Objekte unveränderlich (immutable) sind. Die Zeile

public record Rectangle(double length, double width) { }

definiert eine Klasse mit den unveränderlichen Instanzvariablen length und width, die über die gleichnamigen Methoden ausgelesen werden können (length() und width()). Einen geeigneten Konstruktor bekommen Sie frei Haus geliefert, ebenso brauchbare Implementierungen von toString, equals und hashCode.

Bei Bedarf können Sie Records mit eigenen Methoden und (mit Einschränkungen) mit eigenem Konstruktor-Code ausstatten. Records können Schnittstellen implementieren, aber keine Vererbung nutzen (also weder selbst erben noch als Basis für Vererbung dienen).

Besonders gut eignen sich Records für Data Transfer Objects (DTOs). Dieser Fachbegriff bezeichnet den Datenaustausch zwischen Methoden oder anderen Software-Komponenten.

Mehrzeilige Zeichenketten

Ebenfalls jahrelang auf der Wunschliste vieler Java-Entwickler stand die Möglichkeit, mehrzeilige Zeichenketten zu formulieren. Wie in einigen anderen Programmiersprachen werden solche Zeichenketten mit """ eingeleitet und abgeschlossen.

String sql = """
    SELECT books.id, title, publId, name 
    FROM books 
    JOIN publishers ON publId = publishers.id  
    WHERE books.id >= 167""";

// entspricht in älteren Java-Versionen:
String sql =
    "SELECT books.id, title, publId, name\n" +
    "FROM books\n" +
    "JOIN publishers ON publId = publishers.id\n" +
    "WHERE books.id >= 167";

Die neue Syntax mit """ hat drei Besonderheiten:

  • Nach den einleitenden Zeichen """ muss eine neue Zeile folgen.

  • Nur wenn die abschließenden Zeichen """ in einer neuen Zeile angegeben
    werden, endet auch die letzte Zeile mit dem Zeilenumbruchzeichen \n.

  • Die Position der abschließenden Zeichen """ bestimmt die Einrücktiefe des
    Texts.

Der letzte Punkt erfordert noch einmal ein Beispiel:

String s = """
       abc
         efg
    """; 
// entspricht s = "   abc\n" +
//                "     efg\n";

Weil """ hier gegenüber dem Inhalt der Zeichenkette drei Zeichen weiter links platziert wurde, beginnt jede Zeile der Zeichenkette mit drei Leerzeichen.

Endlich lesbare switch-Ausdrücke

Die in den Grundzügen von C übernommene Syntax von switch war in der Vergangenheit so, dass man Einsteigern eigentlich nur davon abraten konnte. Die alte Syntax bleibt weiterhin gültig, aber parallel dazu besteht nun eine zweite Variante, die in manchen Fällen tatsächlich präzisen, gut lesbaren Code liefert:

switch(ausdruck) {
    case konstante1, k2 -> { anweisung1a; a1b; }
    case k3, k4, k5     -> { a2a; a2b; a3b}
    default             -> { a3a; a3b; }
}

Wenn der switch-Ausdruck dazu dient, einen Wert einzustellen, gibt es noch eine Vereinfachung. Wie in Kotlin können Sie die gesamte switch-Konstruktion als Ausdruck betrachten und das Ergebnis direkt einer Variablen zuweisen. Vergessen Sie nicht, dass Sie in diesem Fall den gesamten Ausdruck mit einem Strichpunkt beenden müssen!

int result = switch(ausdruck) {
               case konstante1, k2 -> 1;
               case k3, k4, k5     -> 2;
               default             -> -1;
             };

Abschließend noch ein konkretes Beispiel:

String month = "März";
int days =  
    switch(month) {
        case "Januar", "März", "Mai", "Juli", "August",
             "Oktober", "Dezember" -> 31; 
        case "April", "Juni", "September", 
             "November" -> 30;      
        case "Februar"  -> 28; // oder 29, wenn Schaltjahr
        default -> 0;
    };

Mehr Komfort für instanceof

Der Operator instanceof wird häufig vor einem Downcast verwendet, um sicherzustellen, dass die Variable tatsächlich ein Objekt des erforderlichen Typs enthält:

if(bike instanceof EBike) {
    var eb = (EBike)bike;             
    // eb gibt Zugriff auf EBike-spezifische Methoden und -Variablen 
}

In Java 17 lässt sich der Code wie folgt verkürzen:

if(bike instanceof EBike eb) {
    // eb gibt Zugriff auf EBike-spezifische Methoden und -Variablen 
}

Innerhalb des if-Blocks steht nun die Variable eb mit dem Datentyp EBike zur Verfügung.

Nächste LTS-Version vermutlich schon 2023

Oracle plant, auf einen zweijährigen LTS-Zyklus umzusteigen. Ganz fix ist das noch nicht, aber wenn der Plan realisiert wird, dann wird Java 21 die nächste LTS-Version sein und im Herbst 2023 erscheinen.

Fazit

Ich bleibe bei meiner Ansicht: Das bessere Java heißt heute Kotlin.

Aber man muss Oracle zugestehen: Im Rahmen des engen Kompatibilitätskorsetts ist gelungen, Java in wesentlichen Details zu modernisieren. Begeisterung ruft das kaum hervor, aber es natürlich dennoch ein Fortschritt für alle, die im Java-Kosmos gefangen sind. Die Dominanz von Java in bestimmten Segmenten der Software-Industrie wird damit weiter zementiert.

Quellen, Links, Downloads