java 11 featuresjava 11 features
HappyCoders Glasses

Java 11 Features
(mit Beispielen)

Sven Woltmann
Sven Woltmann
26. Oktober 2021

Mit Java 11 wurde am 25. September 2018 das erste Long-Term-Support (LTS)-Release des JDK seit der Umstellung auf den sechsmonatige Release-Zyklus veröffentlicht. "Long-Term-Support" bedeutet, dass Oracle diese Version für mehrere Jahre mit Sicherheitspatches ausstatten wird.

Die letzte LTS-Version war Java 8. Java 9 und 10 waren keine LTS-Releases, was bedeutet, dass der Support dieser Versionen mit der jeweils folgenden Version eingestellt wurde.

Ich habe die Änderungen in Java 11 nach Relevanz für die tägliche Entwicklerarbeit sortiert. Zuerst kommen Änderungen an der Sprache selbst. Es folgen Erweiterungen an der JDK-Klassenbibliothek, Tools und experimentelle Features. Und zuletzt Deprecations, Löschungen und sonstige kleine Änderungen.

Wichtig ist noch zu wissen, dass das Oracle-JDK ab Version 11 nur noch für Entwickler frei nutzbar ist. Firmen benötigen einen kostenpflichtigen Supportvertrag mit Oracle. OpenJDK 11 hingegen ist für alle frei einsetzbar.

Local-Variable Syntax for Lambda Parameters

Mit dem JDK Enhancement Proposal 323 wird die Verwendung von "var" in Parametern von implizit typisierten Lambda-Ausdrücken erlaubt.

Was ist ein implizit typisierter Lambda-Ausdruck?

Fangen wir mit einem explizit typisierten Lambda-Ausdruck an. Explizit bedeutet in folgendem Beispiel, dass die Datentypen der Lambda-Parameter l und s – also List<String> und String – mit angegeben sind:

(List<String> l, String s) -> l.add(s);Code-Sprache: Java (java)

Der Compiler kann die Typen allerdings auch aus dem Kontext herleiten, so dass auch folgende – implizit typisierte – Schreibweise erlaubt ist:

(l, s) -> l.add(s);Code-Sprache: Java (java)

Seit Java 11 kann anstelle der expliziten Typen auch das in Java 10 eingeführte "var" verwendet werden:

(var l, var s) -> l.add(s);Code-Sprache: Java (java)

Doch warum sollte man "var" schreiben, wenn man die Typen, wie im Beispiel davor, komplett weglassen kann?

Der Grund dafür sind Annotationen. Soll eine Variable annotiert werden, muss die Annotation am Typ stehen – das kann ein expliziter Typ sein oder auch "var". Eine Annotation direkt am Variablennamen ist nicht erlaubt.

Möchte man die Variablen im obigen Beispiel-Lambda annotieren, war bisher nur folgende Schreibweise möglich:

(@Nonnull List<String> l, @Nullable String s) -> l.add(s);Code-Sprache: Java (java)

Java 11 erlaubt mit "var" nun auch folgende, kürzere Variante:

(@Nonnull var l, @Nullable var s) -> l.add(s);Code-Sprache: Java (java)

Die verschiedenen Schreibweisen dürfen nicht gemischt werden. D. h. du musst entweder für alle Variablen den Typ angeben, alle Typen weglassen oder bei allen Variablen "var" verwenden.

Welche Form du letztendlich wählst, hängt von der Lesbarkeit im konkreten Fall und den Stil-Vorgaben deines Teams ab.

HTTP Client (Standard)

Um vor Java 11 mit JDK-Hausmitteln Daten z. B. per HTTP-POST zu versenden, war eine ganze Menge Code erforderlich.

(Das folgende Beispiel verwendet dabei das in Java 8 hinzugekommene BufferedReader.lines(), um die Antwort als Stream zu lesen und per Collector zu einem String zusammenzufassen. Vor Java 8 waren hierfür noch einige Zeilen mehr nötig.)

public String post(String url, String data) throws IOException {
  URL urlObj = new URL(url);
  HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
  con.setRequestMethod("POST");
  con.setRequestProperty("Content-Type", "application/json");

  // Send data
  con.setDoOutput(true);
  try (OutputStream os = con.getOutputStream()) {
    byte[] input = data.getBytes(StandardCharsets.UTF_8);
    os.write(input, 0, input.length);
  }

  // Handle HTTP errors
  if (con.getResponseCode() != 200) {
    con.disconnect();
    throw new IOException("HTTP response status: " + con.getResponseCode());
  }

  // Read response
  String body;
  try (InputStreamReader isr = new InputStreamReader(con.getInputStream());
      BufferedReader br = new BufferedReader(isr)) {
    body = br.lines().collect(Collectors.joining("\n"));
  }
  con.disconnect();

  return body;
}Code-Sprache: Java (java)

Das JDK 11 enthält die neue Klasse HttpClient, die die Arbeit mit HTTP wesentlich vereinfacht.

Den Code oben kann man mit HttpClient deutlich kürzer und aussagekräftiger schreiben:

public String post(String url, String data) throws IOException, InterruptedException {
  HttpClient client = HttpClient.newHttpClient();

  HttpRequest request =
      HttpRequest.newBuilder()
          .uri(URI.create(url))
          .header("Content-Type", "application/json")
          .POST(BodyPublishers.ofString(data))
          .build();

  HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

  if (response.statusCode() != 200) {
    throw new IOException("HTTP response status: " + response.statusCode());
  }

  return response.body();
}Code-Sprache: Java (java)

Die neue HttpClient-Klasse ist vielseitig einsetzbar:

  • Über BodyPublishers.ofString() senden wir im Beispiel oben einen String. Über die Methoden ofByteArray(), ofByteArrays(), ofFile() und ofInputStream() der gleichen Klasse können wir die zu sendenden Daten auch aus anderen Quellen lesen.
  • Analog kann die Klasse BodyHandlers, mit deren ofString()-Methode wir die Antwort als String geliefert bekommen, auch Byte-Arrays und Streams liefern oder die empfangene Antwort in einer Datei speichern.
  • HttpClient unterstützt im Gegensatz zur bisherigen Lösung auch HTTP/2 and WebSocket.
  • Außerdem bietet HttpClient neben dem oben gezeigten synchronen Programmiermodell auch ein asynchrones Modell. Die Methode HttpClient.sendAsync() gibt dabei ein CompletableFuture zurück, mit dem wir dann asynchron weiterarbeiten können.

Eine asynchrone Variante der Post-Methode kann z. B. wie folgt aussehen:

public void postAsync(
    String url, String data, Consumer<String> consumer, IntConsumer errorHandler) {
  HttpClient client = HttpClient.newHttpClient();

  HttpRequest request =
      HttpRequest.newBuilder()
          .uri(URI.create(url))
          .header("Content-Type", "application/json")
          .POST(BodyPublishers.ofString(data))
          .build();

  client
      .sendAsync(request, BodyHandlers.ofString())
      .thenAccept(
          response -> {
            if (response.statusCode() == 200) {
              consumer.accept(response.body());
            } else {
              errorHandler.accept(response.statusCode());
            }
          });
}
Code-Sprache: Java (java)

(HttpClient ist definiert im JDK Enhancement Proposal 321.)

Neue Collection.toArray()-Methode

Bisher bot das Collection-Interface zwei toArray()-Methoden, um Collections in Arrays zu konvertieren. Das folgende Beispiel zeigt diese zwei Methoden (und zwei unterschiedliche Anwendungen der zweiten Methode) am Beispiel einer Liste von Strings:

List<String> list = List.of("foo", "bar", "baz");

Object[] strings1 = list.toArray();

String[] strings2a = list.toArray(new String[list.size()]);
String[] strings2b = list.toArray(new String[0]);
Code-Sprache: Gherkin (gherkin)

Die erste toArray()-Methode ohne Parameter liefert ein Objekt-Array zurück, da aufgrund von Type Erasure zur Laufzeit die Typ-Information von list nicht mehr bekannt ist.

Der zweiten toArray()-Methode übergeben wir ein Array des gewünschten Typs. Wenn dieses Array mindestens so groß ist wie die Collection, werden die Elemente in diesem Array hinterlegt (strings2a). Andernfalls wird ein neues Array in der erforderlichen Größe erstellt (strings2b).

Seit Java 12 können wir auch folgendes schreiben:

String[] strings = list.toArray(String[]::new);Code-Sprache: Java (java)

Diese Methode erlaubt es den Collection-Klassen anhand der übergebenen Referenz auf den Array-Konstruktor selbst ein Array der benötigten Größe zu erstellen.

Allerdings wird diese Möglichkeit nicht (oder selten?) genutzt. Die Methode ist lediglich im Collection-Interface implementiert. Sie erstellt ein leeres Array und ruft dann die bisherige toArray()-Methode auf:

default <T> T[] toArray(IntFunction<T[]> generator) {
  return toArray(generator.apply(0));
}Code-Sprache: Java (java)

Ich habe nicht alle Collection-Implementierungen untersucht. Aber alle, die ich mir angeschaut haben, überschreiben diese neue Methode nicht. Wenn du eine Collection-Klasse kennst, die diese Methode überschreibt, schreib mir gerne einen Kommentar.

(Die neue toArray()-Methode ist nicht in einem JDK Enhancement Proposal definiert.)

Neue String-Methoden

In Java 11 wurde die Klasse String um einige hilfreiche Methoden erweitert:

String.strip(), stripLeading(), stripTailing()

Die Methode String.strip() entfernt aus einem String alle führenden und abschließenden Leerzeichen.

Haben wir dafür nicht schon die Methode String.trim()?

Ja, mit dem folgenden Unterschied:

  • trim() entfernt alle Zeichen, deren Codepoint U+0020 oder kleiner ist. Das schließt z. B. "Space", "Tab", "Newline" und "Carriage Return" ein.
  • strip() entfernt diejenigen Zeichen, die Character.isWhitespace() als Leerzeichen einstuft. Das sind zum einen einige (aber nicht alle) Zeichen mit Codepoint U+0020 oder kleiner. Und zum andere Zeichen, die im Unicode-Standard als Leerzeichen, Zeilenumbrüche und Absatztrennzeichen definiert sind (z. B. U+2002 – ein Leerzeichen, das so breit ist wie der Buchstabe 'n').

Es gibt noch zwei Varianten der Methode: stripLeading() entfernt nur führende Leerzeichen, stripTailing() nur abschließende.

String.isBlank()

Die Methode String.isBlank() liefert genau dann true zurück, wenn der String nur solche Zeichen enthält, die das im vorangegangenen Punkt genannte Character.isWhitespace() als Leerzeichen einstuft.

String.repeat()

Mit String.repeat() wird ein String wiederholt aneinandergereiht:

System.out.println(":-) ".repeat(10));

⟶

:-) :-) :-) :-) :-) :-) :-) :-) :-) :-) 
Code-Sprache: GLSL (glsl)

String.lines()

Die Methode String.lines() trennt einen String an Zeilenumbrüchen auf und liefert einen Stream aller Zeilen zurück.

Hier ist ein kleines Beispiel:

Stream<String> lines = "foo\nbar\nbaz".lines();
lines.forEachOrdered(System.out::println);

⟶

foo
bar
bazCode-Sprache: Java (java)

(Für die Erweiterungen der String-Klasse gibt es kein JDK Enhancement Proposal.)

Files.readString() und writeString()

Das Lesen und Schreiben von Textdateien wurde seit Java 6 kontinuierlich vereinfacht. In Java 6 mussten wir noch einen FileInputStream öffnen, diesen mit einem InputStreamReader und einem BufferedReader wrappen, dann die Textdatei Zeile für Zeile in einen StringBuilder laden (oder, alternativ, den BufferedReader weglassen und die Daten in char[]-Blöcken auslesen) und im finally-Block die Reader und den InputStream wieder schließen.

Zum Glück gab es Libraries wie Apache Commons IO, die uns mit den Methoden FileUtils.readFileToString() und writeFileToString() diese Arbeit abnahm.

In Java 7 konnten wir die ineinander geschachtelte Stream/Reader- bzw. Stream/Writer-Kombination mit Files.newBufferedWriter() und Files.newBufferedReader() deutlich einfacher erzeugen. Und dank try-with-resources brauchten wir auch keinen finally-Block mehr.

Dennoch waren noch immer mehrere Zeilen Code notwendig:

public static void writeStringJava7(Path path, String text) throws IOException {
  try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
    writer.write(text);
  }
}

private static String readFileJava7(Path path) throws IOException {
  StringBuilder sb = new StringBuilder();
  try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
      sb.append(line).append('\n');
    }
  }
  return sb.toString();
}Code-Sprache: Java (java)

Mit Java 11 bekommen wir endlich Methoden, die es mit denen Drittanbieter-Bibliotheken aufnehmen können:

Files.writeString(path, text, StandardCharsets.UTF_8);

text = Files.readString(path, StandardCharsets.UTF_8);Code-Sprache: Java (java)

An dieser Stelle möchte ich ein wenig Vorfreude auf Java 18 wecken: JEP 400 wird UTF-8 endlich als Standardzeichensatz für alle Architekturen und Betriebssysteme festlegen, so dass wir den Zeichensatz-Parameter dann guten Gewissens weglassen können.

(Für diese Erweiterung der Klassenbibliothek gibt es kein JDK Enhancement Proposal.)

Path.of()

Path-Objekte mussten wir bisher über Paths.get() erstellen – oder File.toPath(). Die Einführung von default-Methoden in Interfaces in Java 8 erlaubte es den JDK-Entwicklern entsprechende Factory-Methoden direkt in das Path-Interface zu integrieren.

Seit Java 11 kannst du Path-Objekte beispielsweise wie folgt erstellen:

// Relative path foo/bar/baz
Path.of("foo/bar/baz");
Path.of("foo", "bar/baz");
Path.of("foo", "bar", "baz");

// Absolute path /foo/bar/baz
Path.of("/foo/bar/baz");
Path.of("/foo", "bar", "baz");
Path.of("/", "foo", "bar", "baz");Code-Sprache: Java (java)

Als Parameter kannst du den gesamten Pfad oder Teile des Pfades angeben – in beliebigen Kombinationen, wie im Beispiel zu sehen.

Um einen absoluten Pfad zu definieren, muss der erste Teil unter Linux und macOS mit "/" beginnen und unter Windows mit einem Laufwerksbuchstaben, wie z. B. "C:".

(Für diese Erweiterung der Klassenbibliothek gibt es kein JDK Enhancement Proposal.)

Epsilon: A No-Op Garbage Collector

Mit dem JDK 11 erhalten wir einen neuen Garbage Collector: den Epsilon GC.

Der Epsilon GC macht ... nichts. Nun, nicht ganz. Er verwaltet die Allokation von Objekten auf dem Heap – er hat aber keinen Garbage-Collection-Prozess, mit dem er die Objekte wieder freigibt.

Welchen Zweck hat ein Garbage Collector, der keinen Garbage collected?

Folgende Einsatzszenarien sind denkbar:

  • Performance-Tests: Bei Micro-Benchmarks z. B., bei denen man verschiedene Implementierungen von Algorithmen miteinander vergleicht, ist ein regulärer Garbage Collector hinderlich, da er die Ausführungszeiten beeinflussen und damit die Messergebnisse verfälschen kann. Durch die Verwendung des Epsilon GC können solche Beeinflussungen ausgeschlossen werden.
  • Extrem kurzlebige Applikationen, wie z. B. für AWS Lambda entwickelte, sollen so schnell wie möglich beendet werden. Ein Garbage-Collection-Zyklus wäre hier Zeitverschwendung, wenn die Applikation ohnehin wenige Millisekunden später wieder beendet wird.
  • Eliminierung von Latenzen: Wenn Entwicklerinnen und Entwickler den Speicherbedarf ihrer Anwendung gut kennen und komplett oder nahezu komplett auf Objekt-Allokationen verzichten, ermöglicht ihnen der Epsilon GC eine latenzfreie Anwendung zu implementieren.

Epsilon GC wird – analog zu allen anderen Garbage Collectoren – über folgende Option in der java-Befehlszeile aktiviert:

-XX:+UseEpsilonGC

(Epsilon GC ist definiert im JDK Enhancement Proposal 318.)

Launch Single-File Source-Code Programs

Für kleine Java-Programme, die aus nur einer Klasse bestehen, wird der JDK Enhancement Proposal 330 interessant.

Damit ist es zum einen möglich eine .java-Datei mit dem java-Kommando zu compilieren und auszuführen. Zum anderen kann eine .java-Datei durch ein sogenanntes "Shebang" direkt ausführbar gemacht werden.

Was genau das bedeutet, zeige ich dir an einem einfachen Beispiel.

Erstelle dazu eine Datei mit dem Namen Hello.java und folgendem Inhalt:

public class Hello {
  public static void main(String[] args) {
    if (args.length > 0) {
      System.out.printf("Hello %s!%n", args[0]);
    } else {
      System.out.println("Hello!");
    }
  }
}Code-Sprache: Java (java)

Bisher musstest du dieses Programm zuerst mit javac compilieren, um es dann mit java ausführen zu können:

$ javac Hello.java
$ java Hello Anna

⟶

Hello Anna!Code-Sprache: Klartext (plaintext)

Ab Java 11 kannst du den ersten Schritt weglassen:

$ java Hello.java Anna

⟶

Hello Anna!Code-Sprache: Klartext (plaintext)

Der Quellcode wird dabei in den Arbeitsspeicher compiliert und von dort ausgeführt.

Auf Linux und macOS kannst du noch einen Schritt weiter gehen und direkt ein ausführbares Java-Skript schreiben. Dazu musst du in der ersten Zeile ein sogenanntes "Shebang" und die Java-Version eintragen:

#!/usr/bin/java --source 11

public class Hello {
  public static void main(String[] args) {
    if (args.length > 0) {
      System.out.printf("Hello %s!%n", args[0]);
    } else {
      System.out.println("Hello!");
    }
  }
}Code-Sprache: Java (java)

Die Datei darf keine .java-Endung haben. Benenne sie um in Hello und mache sie ausführbar:

mv Hello.java Hello
chmod +x HelloCode-Sprache: Klartext (plaintext)

Jetzt kannst du sie direkt ausführen:

$ ./Hello Anna

⟶

Hello Anna!Code-Sprache: Klartext (plaintext)

Ziemlich cool, oder? Für kleinere Tools kann das sehr nützlich sein.

Nest-Based Access Control

Bei der Verwendung von inneren Klassen bekommen wir Java-Entwicklerinnen und -Entwickler immer wieder die folgende Warnung zu sehen:

Synthetic-accessor-Warnung in IntelliJ
Synthetic-accessor-Warnung in IntelliJ

Was hat es damit auf sich?

Die Java Language Specification (JLS) erlaubt den Zugriff auf private Felder und Methoden von inneren Klassen. Die Java Virtual Machine (JVM) hingegen erlaubt das (bisher) nicht.

Um diesen Widerspruch aufzulösen, fügt der Java-Compiler (bis Java 10) beim Zugriff auf diese privaten Felder und Methoden sogenannte "synthetische Accessor-Methoden" ein – mit der Standard-Sichtbarkeit "package-private".

Diese zusätzlichen Methoden führen dazu, dass scheinbar private Felder und Methoden vom gesamten Paket aus zugänglich sind. Dementsprechend erfolgt die Warnung.

Auflösen ließ sich das bisher, indem man die entsprechenden Members entweder selbst package-private machte oder – dies ging nur in Eclipse – den Code mit der Annotation @SuppressWarnings("synthetic-access") versah.

Mit dem JDK Enhancement Proposal 181 wird die Zugriffskontrolle der JVM so erweitert, dass der Zugriff auf private Members von inneren Klassen ohne synthetische Accessor auskommt.

Solltest du Methoden und Felder innerer Klassen aus o. g. Grund package-private gemacht haben oder @SuppressWarnings eingesetzt haben, dann kannst du das mit Java 11 wieder rückgängig machen.

Analyse-Tools

Java Flight Recorder

Zahlreiche Tools helfen uns bei der Fehleranalyse und -behebung während des Entwicklungsprozesses. Bestimmte Probleme treten aber erst im Betrieb einer Anwendung auf. Deren Analyse ist oft schwer bis unmöglich, da wir solche Fehler oft nicht reproduzieren können.

Java Flight Recorder (JFR) kann uns hier zur Seite stehen, indem er während des Betriebs einer Anwendung zahlreiche JVM-Daten aufzeichnet und in einer Datei zur nachträglichen Analyse bereitstellt.

Flight Recorder existiert bereits seit mehrere Jahren als kommerzielles Feature im Oracle-JDK. Mit dem JDK Enhancement Proposal 328 wird er Teil des OpenJDK und kann damit frei eingesetzt werden.

Wie startet man Flight Recorder?

Du kannst Flight Recorder auf zwei Arten starten. Zum einen kannst du ihn beim Start einer Anwendung über folgende Option in der java-Befehlszeile aktivieren:

-XX:StartFlightRecording=filename=<file name>

Zum anderen kannst du jcmd nutzen, um Flight Recorder in einer laufenden Java-Anwendung zu aktivieren:

jcmd JFR.start filename=<file name>

Bei beiden Varianten kannst du zahlreiche weitere Optionen angeben; z. B. kannst du mit "duration" festlegen, wie lange der Recorder laufen soll. Alle Optionen im Detail vorzustellen würde den Rahmen dieses Artikels sprengen. Du findest sie in der offiziellen Flight-Recorder-Dokumentation von Oracle.

Java Flight Recorder Beispiel

In folgendem Beispiel sei 31100 die Prozess-ID der zu analysierenden Java-Anwendung. Die Aufzeichnung wird wie folgt gestartet (über den optionalen Parameter "name" geben wir dabei einen Namen für die Aufzeichnung an):

$ jcmd 31100 JFR.start filename=myrecording.jfr name=myrecording
31100:
Started recording 1. No limit specified, using maxsize=250MB as default.

Use jcmd 31100 JFR.dump name=myrecording to copy recording data to file.Code-Sprache: Klartext (plaintext)

In der Regel speichert Flight Recorder die Aufzeichnung nur in bestimmten Abständen und beim Beenden der Anwendung in die angegebene Datei. Du kannst die Aufzeichnung aber auch zwischendurch manuell speichern, indem du das Dump-Kommando ausführst, das dir beim Start angezeigt wurde:

$ jcmd 31100 JFR.dump name=myrecording
31100:
Dumped recording "myrecording", 344.8 kB written to:

<path>/myrecording.jfrCode-Sprache: Klartext (plaintext)

Falls du beim Start keinen "name"-Parameter angegeben hast, kannst du als Namen die Nummer der Aufzeichnung (im Beispiel oben "1") angeben.

Stoppen kannst du Flight Recorder wie folgt:

$ jcmd 31100 JFR.stop name=myrecording
31100:
Stopped recording "myrecording".Code-Sprache: Klartext (plaintext)

Wie analysiert man nun die von Flight Recorder gespeicherte Datei?

Dazu brauchen wir ein weiteres Tool...

JDK Mission Control

Zur Betrachtung der Analysedaten benötigst du ein weiteres Tool: JDK Mission Control. Auf der GitHub-Seite des Projekts findest du Links zu mehreren Distributoren, bei denen du Mission Control für Windows, Mac und Linux herunterladen kannst.

Über "File / Open File..." lädst du die Analysedatei. Mission Control zeigt dir zunächst eine Übersicht der gesammelten Daten:

JDK Mission Control - Übersicht
JDK Mission Control - Übersicht

Über die Navigation links kannst du dann tiefer in bestimmte Bereiche, wie Threads, Speicherbelegung, Locks, etc... eintauchen. Unter "Threads" z. B. siehst du, welche Threads von wann bis wann liefen:

JDK Mission Control - Threads
JDK Mission Control - Threads

Auch unter den anderen Navigationspunkten findest du spannende Aufbereitungen der gesammelten Daten. Probier es am besten gleich selbst einmal aus!

Low-Overhead Heap Profiling

Ein wichtiges Werkzeug bei der Analyse von Speicherproblemen (z. B. hohe Gargabe-Collector-Zeiten oder OutOfMemoryErrors) sind Heap-Dumps zur Analyse der auf dem Heap liegenden Objekte. Der Markt bietet hierfür zahlreiche Tools an. Was diese Werkzeuge bisher nicht verraten, ist, an welcher Stelle des Programmcodes die auf dem Heap liegenden Objekte erzeugt wurden.

Mit JEP 331 wird das Java Virtual Machine Tool Interface (JVMTI) – also das Interface, über das diese Tools die Daten über die laufende Anwendung beziehen – um die Möglichkeit erweitert, Stack Traces aller Objekt-Allokationen zu sammeln. Die Heap-Analyse Tools können diese Zusatzinformationen mit anzeigen und uns Entwicklern die Problemanalyse so wesentlich erleichtern.

Experimentelle und Preview-Features

Auf experimentelle und Preview-Features werde ich nur kurz eingehen und stattdessen auf die Java-Versionen verweisen, in denen die Features als "production-ready" released werden.

ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

Der "Z Garbage Collector" – kurz ZGC – ist ein von Oracle entwickelter alternativer Garbage Collector mit dem Ziel die Pausezeiten von Full GCs (also vollständigen Collections über alle Heap-Regionen) auf maximal 10 ms zu reduzieren – ohne dabei den Gesamtdurchsatz gegenüber dem G1GC um mehr als 15 % zu reduzieren.

ZGC ist zunächst nur für Linux verfügbar. Aktivieren kannst du ihn mit den folgenden JVM-Flags:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

ZGC wird in Java 15 Produktionsstatus erreichen. Im entsprechenden Teil dieser Serie werde ich diesen neuen Garbage Collector detaillierter beschreiben.

(Dieses experimentelle Release ist im JDK Enhancement Proposal 333 definiert.)

Deprecations und Löschungen

Dieser Abschnitt listet alle als "deprecated" markierten und aus dem JDK entfernten Features auf.

Remove the Java EE and CORBA Modules

Mit dem JDK Enhancement Proposal 320 werden folgende Module aus dem JDK entfernt:

  • java.xml.ws (JAX-WS)
  • java.xml.bind (JAXB)
  • java.activation (JAF)
  • java.xml.ws.annotation (Common Annotations)
  • java.corba (CORBA)
  • java.transaction (JTA)
  • java.se.ee (Aggregatormodul für die sechs zuvor genannten Module)
  • jdk.xml.ws (Tools für JAX-WS)
  • jdk.xml.bind (Tools für JAXB)

Die genannten Technologien wurden ursprünglich für die Java EE-Plattform entwickelt und wurden mit Java 6 in die Standard Edition "Java SE" integriert. In Java 9 wurden sie wieder als "deprecated" markiert und mit Java 11 endgültig entfernt.

Sollten dir diese Libraries beim Upgrade auf Java 11 fehlen, dann kannst du sie z. B. per Maven-Dependencies wieder in dein Projekt ziehen.

Deprecate the Nashorn JavaScript Engine

Die im JDK 8 eingeführte JavaScript-Engine "Nashorn" wurde in Java 11 mit dem JEP 335 als "deprecated for removal" markiert und soll in einer der zukünftigen Versionen vollständig entfernt werden.

Grund dafür sind die rasanten Entwicklungsgeschwindigkeiten von ECMAScript (dem Standard hinter JavaScript) und der node.js-Engine, die eine Weiterentwicklung von Nashorn zu aufwändig gemacht haben.

Deprecate the Pack200 Tools and API

Pack200 ist ein in Java 5 eingeführtes, spezielles Kompressionsverfahren, das insbesondere für .class- und .jar-Dateien höhere Kompressionsraten erreicht als Standardverfahren. Entwickelt wurde Pack200, um in den frühen 2000ern möglichst viel Bandbreite einzusparen.

Der Algorithmus ist jedoch komplex, und die Weiterentwicklungskosten stehen in Zeiten von 100-MBit-Internetleitungen in keinem Verhältnis zum Nutzen mehr.

Daher wurde das Tool mit dem JDK Enhancement Proposal 336 als "deprecated" markiert und soll in einem der nächsten Java-Releases entfernt werden.

JavaFX geht eigene Wege

Beginnend mit Java 11 wird JavaFX (und das dazugehörende Tool javapackager) nicht mehr mit dem JDK ausgeliefert. Du kannst es stattdessen als separates SDK von der JavaFX-Homepage herunterladen.

(Für diese Änderung gibt es kein JDK Enhancement Proposal.)

Sonstige Änderungen in Java 11 (die man als Java-Entwickler nicht unbedingt kennen muss)

Dieser Abschnitt listet weitere Änderungen unter der Haube von Java 11 auf, von denen du wahrscheinlich nicht direkt etwas mitbekommen wirst. Es kann trotzdem sinnvoll sein, die folgenden Abschnitte einmal zu überfliegen.

Unicode 10

Mit dem JDK Enhancement Proposal 327 wurde Java 11 um Support für Unicode 10.0 erweitert.

Was bedeutet das?

Insbesondere die Klassen String und Character mussten um die neuen Codeblöcke und Zeichen erweitert werden. Relevant ist das z. B. für String.toUpperCase() und toLowerCase() sowie für Character.getName() und Character.UnicodeBlock.of().

Hier ist ein kurzes Code-Beispiel (der Unicode-Codepoint 0x1F929 steht für das Emoji 🤩):

System.out.println("name  = " + Character.getName(0x1F929));
System.out.println("block = " + Character.UnicodeBlock.of(0x1F929));Code-Sprache: Java (java)

Bis Java 10 gibt der Code folgendes aus:

name  = null
block = nullCode-Sprache: Klartext (plaintext)

Java 11 hingegen kennt das neue Emoji – was für ein Glück ;-)

name  = GRINNING FACE WITH STAR EYES
block = SUPPLEMENTAL_SYMBOLS_AND_PICTOGRAPHSCode-Sprache: Klartext (plaintext)

Für String.toUpperCase() und toLowerCase() kann ich hier kein sinnvolles Beispiel abdrucken, da die hinzugekommenen exotischen Schriften "Masaram Gondi", "Nushu", "Soyombo" und "Zanabazar Square" von kaum einem System dargestellt werden können.

Improve Aarch64 Intrinsics

Mit dem JDK Enhancement Proposal 315 werden sogenannte "Intrinsics" für die AArch64-Plattform (also 64-Bit ARM-CPUs) verbessert und neue Intrinsics hinuzugefügt.

Intrinsics werden eingesetzt, um anstelle von Java-Code Architektur-spezifischen Assembler-Code auszuführen, was die Performance bestimmter Methoden der JDK-Klassenbibliothek wesentlich verbessert.

Mit diesem JEP werden Intrinsics für trigonometrische Funktionen und die Logarithmus-Funktion hinzugefügt und bestehende Instrinsics für Methoden wie z. B. String.compareTo() und String.indexOf() optimiert.

Transport Layer Security (TLS) 1.3

Das JDK unterstützte bisher die folgenden Sicherheitsprotokolle:

  • Secure Socket Layer (SSL) in der Version 3.0
  • Transport Layer Security (TLS) in den Versionen 1.0, 1.1 und 1.2
  • Datagram Transport Layer Security (DTLS) in den Versionen 1.0 und 1.2

Mit JEP 332 wurde diese Liste um den modernen Sicherheitsstandard TLS 1.3 erweitert.

ChaCha20 and Poly1305 Cryptographic Algorithms

Mit JEP 329 wird das JDK um zwei Kryptografiealgorithmen – ChaCha20 und Poly1305 – erweitert. Diese werden z. B. von die im vorangegangenen Abschnitt genannten Sicherheitsprotokollen eingesetzt.

Key Agreement with Curve25519 and Curve448

Mit JEP 324 werden die Schlüsselaustauschprotokolle des JDK um die sogenannten elliptischen Kurven "Curve25519" und "Curve448" erweitert. Beide Kurven ermöglichen eine besonders schnelle Ver- und Entschlüsselung des zu verwendenden symmetrischen Schlüssels.

Dynamic Class-File Constants

Das .class-Dateiformat wird um die Konstante CONSTANT_Dynamic erweitert, "die Sprachentwicklern und Compiler-Implementierern breitere Optionen für Ausdrucksfähigkeit und Leistung bietet." Solltest du eine Sprache oder einen Compiler entwickeln wollen, findest du weitere Details im JDK Enhancement Proposal 309.

Vollständige Liste aller Änderungen in Java 11

Dieser Artikel hat alle Features von Java 11 vorgestellt, die in JDK Enhancement Proposals definiert sind, sowie Erweiterungen an der JDK-Klassenbibliothek, die keinem JEP zugeordnet sind.

Eine vollständige Liste aller Änderungen findest du in den offiziellen Java 11 Release Notes.

Fazit

Mit Java 11 können wir "var" nun auch in Lambda-Parametern verwenden.

Auf HTTP-Schnittstellen können wir komfortabel mit dem HttpClient zugreifen.

String wurde um einige hilfreiche Funktionen erweitert. Mit Files.readString() und writeString() können wir endlich ohne Drittbibliothek mit nur einer Zeile Code Textdateien lesen und schreiben, und mit Path.of() können wir Path-Objekte prägnanter erstellen als mit Paths.get().

Mit dem Epsilon Garbage Collector können wir Microbenchmarks ohne störende GC-Zyklen durchführen.

Kleine Programme, die aus nur einer Klasse bestehen, können wir mit einem Aufruf von java compilieren und ausführen – oder sie mit einem "Shebang", wie wir es beispielsweise von Perl kennen, direkt ausführbar machen.

Dank Nest-Based Access Control muss der Compiler keine synthetischen Zugriffsmethoden mehr einfügen.

Und mit dem Flight Recorder wurde ein sehr nützliches Analyse-Tool, das bisher nur mit Supportvertrag von Oracle genutzt werden durfte, für alle frei verfügbar gemacht. Ich kann nur empfehlen es einmal auszuprobieren!

Wenn dir dieser Überblick gefallen hat, freue ich mich über einen Kommentar oder wenn du den Artikel über einen der Share-Buttons am Ende teilst. Möchtest du informiert werden, wenn der nächste Artikel online geht? Dann klicke hier, um dich in meinen Mail-Verteiler einzutragen.