Aus Linux-Magazin 06/2022

Neue Features in Java 18

© Creativ Collection

Seit Ende März liegt das neue Java 18 vor. Nach der Langzeitversion Java 17 beginnt damit der nächste Entwicklungszyklus der populären Programmiersprache.

Pünktlich im halbjährlichen Release-Zyklus ist seit Ende März 2022 Java 18 für die allgemeine Verwendung verfügbar. Es enthält wie immer eine Reihe von Spracherweiterungen sowie Verbesserungen an den Bibliotheken und der Laufzeitumgebung. Neun Änderungen stehen mit Java 18 bereit, ein paar weniger als in der Vorgängerversion. Das dürfte daran liegen, dass das OpenJDK-Projekt [1] innerhalb des letzten Jahres sein komplettes Source-Code-Management samt den daran hängenden Prozessen von Mercurial [2] auf Github umgestellt und mit Java 17 eine Langzeitversion herausgebracht hat.

Das neue Java 18 steht unter Linux für die Architekturen x64, PowerPC, 32- und 64-Bit-ARM sowie s390 zum Download [3] bereit. Dazu kommt noch ein Build für das besonders leichtgewichtige Alpine Linux [4] mit der Musl-libc-Bibliothek. Falls Sie sich sich beim Download über den Namen “Eclipse Temurin” wundern: Der Markenname des populärsten OpenJDK-Builds hat sich geändert. Nach dem Beitritt des AdoptOpenJDK-Projekts zur Eclipse Foundation lautet er nicht mehr Adoptium, sondern Temurin. Der neue Name ist kein komplett künstlicher Markenname, sondern ein Anagramm auf Runtime.

Vor vier Jahren untersagte Oracle die kostenlose Verwendung seines Java-Builds für kommerzielle Zwecke, sodass sich die Entwicklergemeinde längst an die neue Download-Adresse gewöhnt hat. Oracle machte zwar mittlerweile eine Rolle rückwärts, der letzte Java-Build des Konzerns lässt sich inzwischen unter der neuen Oracle-Lizenz “No-Fee Terms and Conditions” [5] auch für den kommerziellen Einsatz wieder gratis nutzen. Damit kommt Oracle aber wohl zu spät, die meisten Java-Entwickler haben das noch nicht einmal mitbekommen und bleiben weiter bei der Version von AdoptOpenJDK.

Detailpflege

Im JEP 420 arbeiten Brian Goetz und Gavin Bierman nach wie vor an dem besten aller Switch-Statements und haben einmal mehr die Java-Syntax erweitert. Der Pfeil-Operator (»->«) gegen unkontrolliertes Fallthrough und Vergleich gegen null steht ja schon seit Java 17 zur Verfügung. In Java 18 lassen sich im Switch-Statement nicht nur die Objektwerte testen, sondern auch die Objekttypen. Wie beim erweiterten »instanceof« stehen die Objekte danach typsicher bereit, was kombinierte Prüfungen gegen Typ und Wert besonders einfach und leserlich macht (Listing 1, Zeile 7). Die Änderungen sind noch im Preview, Sie müssen sie beim Kompilieren und Starten mit der Option »–enable-preview« freischalten.

Listing 1

Switch mit Typprüfung

Object[] testObjekts = new Object[] {Math.PI, null, "hallo", -1.0};
for (Object testObjekt : testObjekts) {
  // @start region="beispiel1"
  var resultat = switch (testObjekt) {
    case null -> "ungültiger Wert 'null'";
    case String s -> "String " + s;
    case Double d && d < 0.0 -> "negativer Double " + d;
    case Double d -> "Double";
    default -> "Unbekannter Wert '" + testObjekt + "'";
  }; // @end
  System.out.println(testObjekt + "=>" + resultat);
}

Neben dieser kleinen Syntaxänderung gibt es wieder Detailpflege an der Laufzeitumgebung. Die erste Änderung betrifft dabei weniger englischsprachige Anwender, sondern vor allem den Rest der Welt. Zwar setzt Java intern auf die UTF-8-Kodierung von Texten und hat kein Problem mit europäischen oder asiatischen Sonderzeichen. Beim Einlesen und Schreiben versucht es dagegen bis Java 17, das Default-Encoding des Betriebssystems zu übernehmen. Passt das Encoding in Textdateien nicht mit dem des Betriebssystems überein, sind alle Buchstaben jenseits von A bis Z falsch.

Java 18 stellt diese Automatik ab und verwendet grundsätzlich UTF-8 als Standard für das Lesen und Schreiben, unabhängig von Plattform und Lokalisierung (JEP 400). Damit verhält sich die Software auf allen Systemen gleich, und man erspart sich unangenehme Überraschungen, wenn man Programme auf einem japanischen System mit CP943C-Encoding ausrollt. Wer das Verhalten seiner Software mit einem alten JDK testen möchte, kann bereits Java 8 bis 17 mit der Option »-Dfile.encoding=UTF-8« hart auf UTF-8 umstellen. Andersherum lässt sich das alte Verhalten bei Java 18 mit der Option »-Dfile.encoding=COMPAT« erzwingen.

Ansonsten gilt für das saubere Lesen und Schreiben jenseits von UTF-8 nach wie vor, dass man das korrekte Encoding für Reader und Writer angeben muss. Listing 2 zeigt, wie man das korrekt implementiert, in Abbildung 1 sehen Sie die korrekte und die falsche Ausgabe.

Listing 2

Encoding

// Lesen und Schreiben in ISO-8859-1 statt UTF-8
try (var out = new FileWriter("out.txt", StandardCharsets.ISO_8859_1)) {
  out.write("äöüßGrad");
}
// Lesen und Schreiben in UTF-8 (Standard-Encoding)
try (var in = new BufferedReader(new FileReader("out.txt"))) {
  System.out.println("Eingelesen mit falschem Encoding '" + in.readLine() + "'");
}
// Lesen und Schreiben in ISO-8859-1
try (var in = new BufferedReader(new FileReader("out.txt", StandardCharsets.ISO_8859_1))) {
  System.out.println("Eingelesen mit korrekten Encoding '" + in.readLine() + "'");
}
Abbildung 1: Falsches und korrektes Encoding: Die Ausgabe zu <a href="#artRef-l2">Listing&nbsp;2</a>.

Abbildung 1: Falsches und korrektes Encoding: Die Ausgabe zu Listing 2.

Das automatische Löschen ungenutzter Objekte gehört zu den Kern-Features von Java. Muss das Objekt erst noch weitere Aufräumarbeiten erledigen, ruft der Garbage Collector vorher die Methode »finalize« auf. Seit gut zehn Jahren wird allerdings von der Implementation der »finalize« Methode abgeraten, da man sich leicht Probleme einhandeln kann. Als Alternative gelten die mit Java 9 eingeführten Cleaner und das Try-with-Resource-Pattern.

Um in diesem Bereich aufzuräumen, wurden mit JEP 421 diverse »finalize«-Methoden als obsolet (deprecated) markiert oder gleich entfernt. In Zukunft soll das Feature komplett entfallen, zum Testen schalten Sie es mit der Option »–finalization=disabled« aus.

Lieferdienst

Ein Neuzugang bei den Java-Werkzeugen ist der Webserver JWebServer. JEP 408 empfiehlt ihn als einfache Lösung zum Ausliefern von Hello-World-HTMLs bei Schulungen, da er extrem einfach zu starten ist, wie die letzten vier Zeilen in Abbildung 1 demonstrieren. Er liefert Dokumente aus und erstellt automatisch Übersichtsseiten für Verzeichnisse. Das genügt für eine Informatik-Klasse an der Schule, einen Full-Stack-Entwickler mit Nginx-, Node.js- oder Apache-Tomcat-Installation reißt es nicht vom Hocker. Viel interessanter erscheint es, die JWebServer zugrunde liegenden Klassen für das Entwickeln eigener Webserver zu nutzen.

Gerade für Unittests will man ja nicht einen kompletten Server starten oder möchte mit einem Filter zum Zählen oder Überprüfen von Headern dazwischengrätschen können. Ein komplettes Beispiel zeigt Listing 3. Hier startet die »before«-Methode einen »HttpServer«, nimmt dann den Test vor und stoppt den Server schließlich in der »after«-Methode. Dem Server werden beim Erzeugen ein Handler für die Bereitstellung des Inhalts sowie vor oder nach dem Handler laufende Filter übergeben (Zeile 13).

Der »DummyHandler« ab Zeile 31 gibt nur einen einfachen String zurück. Für einen Unittest kann das ein festes XML oder JSON sein, bei Bedarf hat man über das »HttpExchange«-Objekt aber vollen Zugriff auf die Anfrageparameter. Vor beziehungsweise nach dem Handler kann man noch Filter platzieren; der ab Zeile 9 definierte gibt nur die Anfrage aus und zählt die erfolgten Aufrufe. Die Testmethode nutzt zur Demonstration den »HttpClient« und überprüft schließlich den Statuscode sowie die beim Server angekommenen Anfragen.

So kann man einfach ein Mock-Server für HTTP-Clients erstellen und an beliebigen Stellen eingreifen. »HttpServer« selbst ist keine Neuentwicklung, sondern geht in Teilen auf Java 1.6 zurück. Da er jedoch im Package com.sun.net.httpserver liegt, haben nur wenige Entwickler von ihm gewusst und ihn angewendet. Seit JEP 421 kann man wohl davon ausgehen, dass die übliche Regel, die Finger von com.sun-Packages zu lassen, zumindest für diese Klassen nicht mehr gilt.

Listing 3

Einfacher HTTP-Server

class SimpleWebServerTest {
  private HttpServer mockServer;
  private int aufrufe;
  @BeforeEach
  void before() throws Exception {
    aufrufe=0;
    var handler = new DummyHandler();
    var filter = Filter.beforeHandler("Anfragen Zählen und Zeigen",
                 r -> { System.out.println("[" + r.getRequestMethod() + "] " +
                 r.getRequestURI());
                 aufrufe++; });
    mockServer = HttpServer.create(new InetSocketAddress(8080), 10, "/", handler, filter);
    mockServer.start();
  }
  @AfterEach
  void after() throws Exception {
    mockServer.stop(0);
  }
  @Test
  void test() throws Exception {
    var client = HttpClient.newHttpClient();
    var request =  HttpRequest.newBuilder().uri(URI.create("http://localhost:8080/" + UUID.randomUUID())).build();
    var response =client.send(request, HttpResponse.BodyHandlers.ofString());
    assertEquals(HttpURLConnection.HTTP_OK, response.statusCode(), "status" );
    assertEquals(1, aufrufe, "aurufe #");
  }
  private class DummyHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange exchange) throws IOException {
      byte[] antwort = "Hello World".getBytes();
      exchange.sendResponseHeaders(200, antwort.length);
      OutputStream out = exchange.getResponseBody();
 out.write(antwort);
      out.close();
    }
  };
}

Javadoc

Apropos Werkzeuge: Auch Javadoc hat etwas Detailpflege erfahren. Code wie der aus Listing 1 sah bisher wenig gelungen aus und musste in der Dokumentation separat eingepflegt werden. JEP 413 geht diese beiden Probleme an. Listing 4 enthält die Javadoc zu Listing 1, zeigt Abbildung 2. In Zeile 2 des Listings findet sich ein Beispiel für das neue Tag »@snippet«, in dem man den Code einbettet. Mit dem Tag »@highlight« lassen sich dabei wichtige Wörter hervorheben.

Abbildung 2: Die neue Javadoc-Ausgabe f&uuml;r <a href="#artRef-l4">Listing&nbsp;4</a>.

Abbildung 2: Die neue Javadoc-Ausgabe für Listing 4.

Noch interessanter ist das zweite Beispiel in Zeile 8, das einen Bereich aus einer Datei referenziert. Dabei handelt es sich um den in Listing 1 mit »@start« und »@end« definierten Bereich »beispiel«. Das neue Tag erspart nicht nur die doppelte Eingabe: Referenzieren Sie zum Beispiel Code aus einem Unittest, stellt es sicher, dass er wirklich gültig ist. Damit das ganz bestimmt klappt, geben Sie mit »–snippet-path=$pfad« den passenden Pfad zu den Quellen an. Auf Github findet sich ein komplettes Maven-Projekt mit Beispielen [6].

Listing 4

Javadoc zu Listing 1

/**
 * {@snippet :
 *   try (var out = new FileWriter("out.txt", StandardCharsets.ISO_8859_1)) { // @highlight substring="ISO_8859_1"
 *       out.write("äöüßGrad");
 *   }
 * }
 *
 * {@snippet file="de/groygroy/java18/NewSwitch.java" region="beispiel1" }
 */
public class NewSwitch {
  [...]

Diverses

Rund um das Crypto-API wurden wieder neue Algorithmen hinzugefügt beziehungsweise veraltete entfernt. Den ein oder anderen dürfte der Wechsel des Keystore-Formats positiv überraschen. Bisher kam hier das Java-spezifische JKS zum Einsatz, das nun dem allgemein üblichen PKCS12 weicht. Aus dem mitgelieferten Keystore wurden auch gleich ein paar abgelaufene Root-Zertifikate entfernt.

Die weiteren Änderungen haben für normale Entwickler derzeit eher geringe Bedeutung. Zur Auflösung von Netzwerkadressen nutzt Java den Netzwerkstack des Betriebssystems. Falls Sie hier eingreifen möchten, können Sie mit JEP 418 nun eigene Implementation in Java bereitstellen. Das mag für Testzwecke oder Application Gateways ganz nett sein, aber man kann damit den Netzwerkverkehr auf Server umbiegen, die der Anwender gar nicht ansprechen wollte. Ein komplettes Beispiel findet sich bei Sven Woltmann [7]. Im Endeffekt genügt das Einschleusen eines JARs in den Classpath, um die Netzwerkauflösung zu überschreiben.

Idealerweise keine Nebenwirkungen hat die Neuimplementation der Reflection-Methoden mit Method Handles (JEP 416). Mit Reflection i lassen sich zur Laufzeit einige Probleme sehr elegant lösen, die sonst viel Code erfordern. Aus historischen Gründen enthält die Java-VM aber drei verschiedene Implementationswege für Reflection, die im Zuge der geplanten Erweiterungen alle angefasst werden müssten. Alle drei wurden nun einheitlich auf die mit Java 7 eingeführten Method Handles umgestellt.

Für die Freunde der Vektoralgebra direkt auf der CPU wurde die Vector API (JEP 417) weiterentwickelt, die nun neben Intels Short Vector Math Library auch Scalable Vector Extension (SVE) auf ARM unterstützt. Das Foreign Function & Memory API (JEP 419), das Aufrufe von C-Funktionen aus Java heraus unterstützen soll, befindet sich noch im Incubator-Status. Passend dazu gibt es mit Jextract ein Werkzeug, das Klassen dafür aus C-Headern erzeugt [8].

Artverwandte

Rund um die Java-VM gedeiht ein riesiges Biotop aus weiteren Werkzeugen und Bibliotheken. Parallel zur JVM entwickelt Gluon die Oberflächenbibliothek JavaFX [9], die nun ebenfalls in Version 18 vorliegt. Sie bringt unter anderem neuere Media-Codecs mit und ermöglicht die Kombination von HTML View mit weiteren Widgets. Dazu passend gibt es auch eine neue Version des Scene Builders, mit dem sich JavaFX-Oberflächen bequem per Drag & Drop gestalten lassen.

Das Aussehen von JavaFX kann man über Cascading Style Sheets bestimmen. Dirk Lemmermann bringt mit ShowcaseFX [10] das passende Werkzeug, um sie schnell auszuprobieren (Abbildung 3). Seine Webseite JFX-Central [11] hat sich in den letzten Monaten zum Zentralorgan für Informationen rund um JavaFX gemausert.

Abbildung 3: ShowcaseFX beschleunigt die Entwicklung von JavaFX-CSS.

Abbildung 3: ShowcaseFX beschleunigt die Entwicklung von JavaFX-CSS.

Egal ob mit oder ohne Oberflächen: Selbst entwickelte Bibliotheken und Anwendungen müssen letztendlich in nutzerfreundlicher Form als Maven-Bundle, RPM-Package oder Docker-Image bereitgestellt werden. Der Jreleaser [12] nimmt sich dieser Aufgabe an und erstellt und publiziert die gewünschten Artefakte sowohl von der Kommandozeile aus als auch über Continuous-Integration-Tools.

Fazit

Das im letzten Jahr veröffentlichte Java 17 ist inzwischen auf einem guten Weg, Version 11 als Standard abzulösen. Wesentliche Infrastrukturkomponenten wie Apache Tomcat 6.0 oder Spring Boot 5.3 unterstützen es inzwischen. Bis die Sprach-Features von Java 17 auf breiter Front genutzt werden, dürfte wohl noch ein Jahr vergehen. Mit Java 18 beginnt jetzt der nächste Entwicklungszyklus, für den täglichen Einsatz spricht vor allem die Verbesserung an Javadoc.

Die Änderungen an der Netzwerkauflösung erscheinen eher zweischneidig, ermöglichen sie doch auch einen Missbrauch. Davon hatten Java-Entwickler dieses Jahr schon genug zu berichten: Mit Log4shell und Spring4shell wiesen zwei weitverbreitete Bibliotheken bedeutende Sicherheitslücken auf. Die sind zwar inzwischen geschlossen, doch würde man sich in Zukunft ein etwas vorsichtigeres Agieren wünschen. Dem Java-Ökosystem als Ganzem tut das jedoch keinen Abbruch, ähnliche Fehler können schließlich auch in Python-Bibliotheken enthalten sein. (jlu)

Der Autor

Carsten Zerbst erstellt mit seinem Team Software für Ingenieure, vom einfachen Konverter bis hin zur unternehmenskritischen Integrationslösung. Er bietet dieses Jahr noch eine Stelle für abwechslungsreiche Tätigkeiten in Hamburg an.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben