Dipl.-Inform. Michael Inden ist Oracle-zertifizierter Java-Entwickler. Nach seinem Studium in Oldenburg hat er bei diversen internationalen Firmen in verschiedenen Rollen etwa als Softwareentwickler, -architekt, Consultant, Teamleiter sowie Trainer gearbeitet. Zurzeit ist er als CTO und Leiter Academy in Zürich tätig.

Michael Inden hat über zwanzig Jahre Berufserfahrung beim Entwurf komplexer Softwaresysteme gesammelt, an diversen Fortbildungen und mehreren Java-One-Konferenzen teilgenommen. Sein besonderes Interesse gilt dem Design qualitativ hochwertiger Applikationen mit ergonomischen GUIs sowie dem Coaching. Sein Wissen gibt er gerne als Trainer in internen und externen Schulungen und auf Konferenzen weiter, etwa bei der Java User Group Switzerland, bei der JAX/W-JAX, ch.open, der Oracle Code One und den IT-Tagen.

Zu diesem Buch – sowie zu vielen weiteren dpunkt.büchern – können Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+:

www.dpunkt.plus

Michael Inden

Java – die Neuerungen in Version 9 bis 14

Modularisierung, Syntax- und API-Erweiterungen

Michael Inden

Lektorat: Dr. Michael Barabas

Bibliografische Information der Deutschen Nationalbibliothek

1. Auflage 2020

Hinweis:

Schreiben Sie uns:

Die vorliegende Publikation ist urheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags urheberrechtswidrig und daher strafbar. Dies gilt insbesondere für die Vervielfältigung, Übersetzung oder die Verwendung in elektronischen Systemen.

5 4 3 2 1 0

Inhaltsverzeichnis

1Einleitung

INeuerungen in Java 9 bis 11

2Syntaxerweiterungen in JDK 9 bis 11

2.1Anonyme innere Klassen und der Diamond Operator

2.2Erweiterung der @Deprecated-Annotation

2.3Private Methoden in Interfaces

2.4Verbotener Bezeichner ’_

2.5Syntaxerweiterung var (JDK 10 und 11)

3Neues und Änderungen in JDK 9

3.1Neue und erweiterte APIs

3.1.1Das neue Process-API

3.1.2Collection-Factory-Methoden

3.1.3Reactive Streams und die Klasse Flow

3.1.4Erweiterungen in der Klasse InputStream

3.1.5Erweiterungen rund um die Klasse Optional<T>

3.1.6Erweiterungen im Stream-API

3.1.7Neue Kollektoren im Stream-API

3.1.8Erweiterungen in der Datumsverarbeitung

3.1.9Erweiterungen in der Klasse Arrays

3.1.10Erweiterungen in der Klasse Objects

3.1.11Erweiterungen in der Klasse CompletableFuture<T>

3.2Sonstige Änderungen

3.2.1Optimierung bei Strings

3.2.2Deprecation diverser Typen und Methoden im JDK

4Neues und Änderungen in Java 10

4.1Neue und erweiterte APIs

4.1.1Unveränderliche Kopien von Collections

4.1.2Immutable Collections aus Streams erzeugen

4.1.3Erweiterung in der Klasse Optional<T>

4.1.4Modifikationen in der Versionierung

4.1.5Erweiterung in Reader

5Neues und Änderungen in Java 11

5.1Neue und erweiterte APIs

5.1.1Hilfsmethoden in der Klasse String

5.1.2Hilfsmethoden in der Utility-Klasse Files

5.1.3Erweiterung in der Klasse Optional<T>

5.1.4Erweiterung im Interface Predicate<T>

5.1.5HTTP/2-API

5.2Deprecations und Entfernungen im JDK

5.2.1Aufräumarbeiten in der Klasse Thread

5.2.2Deprecation der JavaScript-Unterstützung

5.2.3Ausgliederung von JavaFX

5.2.4Ausgliederung von Java EE und CORBA

6JVM-Änderungen in JDK 9 bis 11

6.1Änderung des Versionsschemas

6.2Java + REPL => jshell

6.3HTML5 Javadoc

6.4Epsilon Garbage Collector (JDK 11)

6.5Launch Single-File Source-Code Programs (JDK 11)

7Übungen zu den Neuerungen in JDK 9 bis 11

IINeuerungen in Java 12 bis 14

8Neues und Änderungen in Java 12

8.1Microbenchmark Suite

8.1.1Eigene Microbenchmarks und Varianten davon

8.1.2Microbenchmarks mit JMH

8.1.3Fazit zu JMH

8.2API-Neuerungen

8.2.1Neue Methoden in der Klasse String

8.2.2Neue Utility-Klasse CompactNumberFormat

8.2.3Neue Hilfsmethode in der Utility-Klasse Files

8.2.4Der teeing()-Kollektor

9Neues und Änderungen in Java 13 und 14

9.1Switch Expressions

9.1.1Einführendes Beispiel

9.1.2Weitere Gründe für die Neuerung

9.1.3yield mit Rückgabewert

9.2Verbesserung bei NullPointerExceptions

9.3Preview-Features

9.3.1Text Blocks

9.3.2Records

9.3.3Pattern Matching bei instanceof

9.4Java 14 – notwendige Anpassungen für Build-Tools und IDEs

9.4.1Java 14 mit Gradle

9.4.2Java 14 mit Maven

9.4.3Java 14 mit Eclipse

9.4.4Java 14 mit IntelliJ

9.4.5Java 14 mit JShell oder der Kommandozeile

9.5Fazit

10Übungen zu den Neuerungen in JDK 12 bis 14

IIIModularisierung

11Modularisierung mit Project Jigsaw

11.1Grundlagen

11.1.1Bisherige Varianten der Modularisierung

11.1.2Warum Modularisierung wünschenswert ist

11.2Modularisierung im Überblick

11.2.1Grundlagen zu Project Jigsaw

11.2.2Einführendes Beispiel mit zwei Modulen

11.2.3Packaging

11.2.4Linking

11.2.5Abhängigkeiten und Modulgraphen

11.2.6Module des JDKs einbinden

11.2.7Arten von Modulen

11.3Sichtbarkeiten und Zugriffsschutz

11.3.1Sichtbarkeiten

11.3.2Zugriffsschutz an Beispielen

11.3.3Transitive Abhängigkeiten (Implied Readability)

11.4Zusammenfassung

12Weiterführende Themen zur Modularisierung

12.1Empfehlenswertes Verzeichnislayout für Module

12.2Modularisierung und Services

12.2.1Begrifflichkeiten: API, SPI und Service Provider

12.2.2Service-Ansatz in Java seit JDK 6

12.2.3Services im Bereich der Modularisierung

12.2.4Definition eines Service Interface

12.2.5Realisierung eines Service Provider

12.2.6Realisierung eines Service Consumer

12.2.7Kontrolle der Abhängigkeiten

12.2.8Fazit

12.3Modularisierung und Reflection

12.3.1Verarbeitung von Modulen mit Reflection

12.3.2Tool zur Ermittlung von Modulen zu Klassen

12.3.3Besonderheiten bei Reflection

12.4Kompatibilität und Migration

12.4.1Kompatibilitätsmodus

12.4.2Migrationsszenarien

12.4.3Fallstrick bei der Bottom-up-Migration

12.4.4Beispiel: Migration mit Automatic Modules

12.4.5Beispiel: Automatic und Unnamed Module

12.4.6Beispiel: Abwandlung mit zwei Automatic Modules

12.4.7Fazit

12.5Build-Management für modularisierte Applikationen

12.5.1Gradle

12.5.2Maven

12.5.3Eclipse

12.5.4IntelliJ IDEA

12.5.5Fazit

13Übungen zur Modularisierung

IVSchlussgedanken

14Zusammenfassung

VAnhang

ASchnelleinstieg in Java 8

A.1Einstieg in Lambdas

A.1.1Lambdas am Beispiel

A.1.2Functional Interfaces und SAM-Typen

A.1.3Type Inference und Kurzformen der Syntax

A.1.4Methodenreferenzen

A.2Streams im Überblick

A.2.1Streams erzeugen – Create Operations

A.2.2Intermediate und Terminal Operations im Überblick

A.2.3Zustandslose Intermediate Operations

A.2.4Zustandsbehaftete Intermediate Operations

A.2.5Terminal Operations

A.3Neuerungen in der Datumsverarbeitung

A.3.1Die Klasse Instant

A.3.2Die Klassen LocalDate, LocalTime und LocalDateTime

A.3.3Die Klasse Duration

A.3.4Die Klasse Period

A.3.5Datumsarithmetik mit TemporalAdjusters

A.4Diverse Erweiterungen

A.4.1Erweiterungen im Interface Comparator<T>

A.4.2Die Klasse Optional<T>

A.4.3Die Klasse CompletableFuture<T>

BEinführung Gradle

B.1Projektstruktur für Maven und Gradle

B.2Builds mit Gradle

CEinführung Maven

C.1Maven im Überblick

C.2Maven am Beispiel

Literaturverzeichnis

Index

Vorwort

Zunächst einmal bedanke ich mich bei Ihnen, dass Sie sich für dieses Buch entschieden haben. Hierin finden Sie eine Vielzahl an Informationen zu den Neuerungen in der aktuellen Java-Version 14 und in den Vorgängern 9 bis 13. Aufgrund des mittlerweile halbjährlichen Releasezyklus sind in den Java-Versionen 10, 11, 12 und 13 jeweils weniger Änderungen als in früheren Releases enthalten. In diesem Buch werden insbesondere auch diverse Neuerungen aus Java 9 beschrieben, weil Java 9 das letzte große Update nach Java 8 war und eine Vielzahl an relevanten Erweiterungen mitbringt.

Zielgruppe

Dieses Buch ist kein Buch für Programmierneulinge, sondern richtet sich an diejenigen Leser, die bereits solides Java-Know-how besitzen und sich kurz und prägnant über die wichtigsten Neuerungen in den Java-Versionen 9 bis 14 informieren wollen. Dieses Buch wendet sich im Speziellen an zwei Zielgruppen:

  1. Zum einen sind dies engagierte Hobbyprogrammierer und Informatikstudenten, aber auch Berufseinsteiger, die Java als Sprache beherrschen und an den Neuerungen in Java 9 bis 14 interessiert sind.
  2. Zum anderen ist das Buch für erfahrene Softwareentwickler und -architekten gedacht, die ihr Wissen ergänzen oder auffrischen wollen, um für zukünftige Projekte abschätzen zu können, ob und – wenn ja – für welche Anforderungen die neuen Java-Versionen eine gewinnbringende Alternative darstellen können.

Was vermittelt dieses Buch?

Sie als Leser erhalten in diesem Buch neben Theoriewissen eine Vertiefung durch praktische Beispiele, sodass der Umstieg auf Java 9 bis 14 in eigenen Projekten erfolgreich gemeistert werden kann. Erleichtert wird ein Umstieg durch eine Vielzahl an Übungen, die dem Leser die jeweiligen Features dediziert nahebringen.

Um die Beispiele des Buchs möglichst präzise und elegant zu halten, verwende ich diverse Features aus Java 8. Deshalb ist es hilfreich, wenn Sie sich damit schon beschäftigt haben. Alle, die eine kleine Auffrischung benötigen, finden zum leichteren Einstieg in Anhang A einen Crashkurs zu Java 8.

Aufbau dieses Buchs

Dieses Buch besteht in Wesentlichen aus zwei größeren Teilbereichen, nämlich einem zu den Neuerungen in Java 9 bis 11 und einem zu den Neuerungen in Java 12 bis 14. Diese Untergliederung habe ich gewählt, weil Java 9 und 10 nicht mehr ohne spezielle Tricks als Download erhältlich sind und Java 11 ein über Jahre unterstütztes Release, ein sogenanntes LTS-Release, darstellt, wobei LTS für Long Term Support steht. Die aktuellsten Entwicklungen sind dann für Java 12 bis 14 beschrieben. Nachfolgend möchte ich die Themen der einzelnen Kapitel kurz vorstellen.

Kapitel 1 – EinleitungDie Einleitung stimmt Sie auf Java 9 bis 14 ein und gibt einen Überblick, was Sie so alles in diesem Buch bzw. als Neuerungen erwartet.

Kapitel 2 – Syntaxerweiterungen in JDK 9 bis 11Zunächst widmen wir uns verschiedenen Änderungen an der Syntax von Java. Neben Details zu Bezeichnern, dem Diamond Operator und Ergänzungen bei Annotations gehe ich vor allem kritisch auf das Feature privater Methoden in Interfaces ein. Für Java 10 und 11 stelle ich var als Möglichkeit zur Definition lokaler Variablen bzw. zur Verwendung in Lambdas vor.

Kapitel 3 – Neues und Änderungen in JDK 9In den APIs des JDKs finden sich diverse Neuerungen. Dieses Potpourri habe ich thematisch ein wenig gegliedert. Neben Vereinfachungen beim Prozess-Handling, der Verarbeitung mit Optional<T> oder von Daten mit InputStreams schauen wir auf fundamentale Neuerungen im Bereich der Concurrency durch Reactive Streams.

Kapitel 4 – Neues und Änderungen in Java 10Seit Java 10 verfolgt man bei Oracle die Strategie, Java in kleinen, aber feinen Iterationen um nützliche Funktionalität zu ergänzen und auch in Bezug auf die Syntax zu modernisieren. In diesem Kapitel werden einige API-Erweiterungen vorgestellt.

Kapitel 5 – Neues und Änderungen in Java 11Neben einigen API-Erweiterungen wurden vor allem kleinere Bereinigungen im JDK vorgenommen, unter anderem sind die Module zu CORBA, JavaFX und in Teilen auch zu XML, speziell JAXB, nun nicht mehr Bestandteil des JDKs.

Kapitel 6 – JVM-Änderungen in JDK 9 bis 11In diesem Kapitel beschäftigen wir uns mit Änderungen in der JVM, etwa bei der Garbage Collection oder der Einführung der jshell. Auch in Bezug auf javadoc und der Nummerierung von Java-Versionen finden wir in Java 9 Änderungen, die thematisiert werden. Java 11 bringt einen neuen Garbage Collector sowie mit dem Feature »Launch Single-File Source-Code Programs« die Möglichkeit, Java-Klassen ohne explizite vorherige Kompilierung ausführen zu können.

Kapitel 7 – Übungen zu den Neuerungen in JDK 9 bis 11Zur Vertiefung des Gelernten präsentiert dieses Kapitel diverse Übungsaufgaben zu den Themen der vorangegangenen Kapitel 2 bis 6.

Kapitel 8 – Neues und Änderungen in Java 12Für uns als Entwickler relevante Neuerungen finden sich in Java 12 kaum. Erwähnenswert ist vor allem ein Preview auf Syntaxänderungen bezüglich switch. Darüber hinaus könnte Sie das Microbenchmark-Framework JMH (Java Microbenchmarking Harness) interessieren, sofern Sie Optimierungen auf Mikroebene vornehmen mussten, um das letzte Quäntchen an Performance herauszuholen. Ergänzend bietet Java 12 dann noch kleinere API-Neuerungen.

Kapitel 9 – Neues und Änderungen in Java 13 und 14Java 13 umfasst vor allem zwei Previews auf Syntaxänderungen: Zum einen ein Folge-Preview zu switch und zum anderen ein Preview auf mehrzeilige Strings, »Text Blocks« genannt. Da diese auch in Java 14 enthalten sind, existiert kein gesondertes Kapitel zu Java 13, sondern alles wird in diesem Kapitel beschrieben. Mit Java 14 werden die Neuerungen bei switch endlich in den Sprachstandard aufgenommen. Zudem gibt es eine hilfreiche Ergänzung zur Fehleranalyse bei NullPointerExceptions. Darüber hinaus finden wir unter anderem folgende Preview-Features: Sogenannte Records, die eine extrem kompakte Schreibweise zum Deklarieren spezieller Klassen mit unveränderlichen Daten bieten und die automatisch das passende API bereitstellen. Im Kontext von instanceof erlaubt es eine Syntaxerweiterung, unschöne Casts und künstliche Hilfsvariablen zu vermeiden. Zudem bietet Java 14 zwei Erweiterungen bei der Definition mehrzeiliger Texte.

Kapitel 10 – Übungen zu den Neuerungen in JDK 12 bis 14Auch für die Neuerungen aus Java 12 bis 14 wird ein umfangreicher Satz an Übungsaufgaben zu den Themen der vorangegangenen Kapitel 8 und 9 präsentiert. Deren Bearbeitung sollte Ihr Wissen dazu vertiefen.

Kapitel 11 – Modularisierung mit Project JigsawKlar strukturierte Softwarearchitekturen mit sauber definierten Abhängigkeiten sind erstrebenswert, um selbst größere Softwaresysteme möglichst beherrschbar zu machen und Teile unabhängig voneinander änderbar zu halten. Seit Java 9 helfen dabei Module als eigenständige Softwarekomponenten. In diesem Kapitel wird die Thematik Modularisierung eingeführt und anhand von Beispielen vorgestellt. Im Speziellen werden Themen wie Sichtbarkeit und Zugriffsschutz behandelt.

Kapitel 12 – Weiterführende Themen zur ModularisierungIn diesem Kapitel gehe ich auf einige fortgeschrittenere Themen zur Modularisierung ein. Zunächst stelle ich Ihnen eine Alternative zum von Oracle propagierten, aber in der Praxis hinderlichen Verzeichnislayout für Module vor. Danach betrachten wir die Abhängigkeitssteuerung in größerer Tiefe: Zwar hilft die Modularisierung bei der Strukturierung eines Systems, jedoch besitzen die Module oftmals direkte Abhängigkeiten bereits zur Kompilierzeit. Wird eine losere Kopplung benötigt, so kann man dafür Services nutzen. Zudem ändern sich durch die Modularisierung ein paar Dinge bezüglich Reflection, beispielsweise lassen sich neue Eigenschaften ermitteln, etwa die Moduldaten zu einer Klasse. Verbleibt noch ein wichtiges Thema, nämlich die Migration einer bestehenden Applikation in eine modularisierte. Weil dabei doch ein paar Dinge zu beachten sind, ist diesem Thema ein ausführlicher Abschnitt gewidmet, der insbesondere die verschiedenen Arten von Modulen und ihre Eigenschaften behandelt.

Kapitel 13 – Übungen zur ModularisierungWie für die API-Erweiterungen werden auch für die Modularisierung verschiedene Übungsaufgaben in einem Kapitel zusammengestellt.

Kapitel 14 – ZusammenfassungDieses Kapitel fasst die Themen rund um die vielfältigen Neuerungen aus Java 9 bis 14 noch einmal kurz zusammen.

Anhang A – Schnelleinsteg in Java 8In Anhang A werden für dieses Buch wesentliche Ergänzungen aus Java 8 rekapituliert. Das erleichtert Ihnen das Verständnis der Neuerungen in aktuellen Java-Versionen, selbst dann, wenn Sie sich noch nicht eingehend mit Java 8 beschäftigt haben. Neben einer Vorstellung der funktionalen Programmierung mit Lambdas widmen wir uns den Streams, einer wesentlichen Neuerung in JDK 8 zur Verarbeitung von Daten. Abgerundet wird Anhang A durch einen kurzen Blick auf das Date and Time API und verschiedene API-Erweiterungen.

Anhang B – Einführung GradleAnhang B liefert eine kurze Einführung in das Build-Tool Gradle, das auch für die Beispiele dieses Buchs zur Übersetzung genutzt wird. Mithilfe des vermittelten Wissens sollten Sie dann auch kleinere eigene Projekte mit einem Build-System ausstatten können.

Anhang C – Einführung MavenIn diesem Anhang wird Maven als Build-Tool kurz vorgestellt. Derzeit bietet es die beste Unterstützung für modularisierte Applikationen in Java. Zudem kann man Maven-Projekte einfach in gängige IDEs importieren.

Sourcecode und ausführbare Programme

Um den Rahmen des Buchs nicht zu sprengen, stellen die Listings häufig nur Ausschnitte aus lauffähigen Programmen dar, wobei wichtige Passagen zum besseren Verständnis mitunter fett hervorgehoben sind. Auf der Webseite zu diesem Buch www.dpunkt.de/java-9-14-die-neuerungen steht dann der vollständige, kompilierbare Sourcecode zu den Programmen zum Download bereit. Neben dem Sourcecode befindet sich auf der Webseite auch ein Eclipse-Projekt, über das sich alle Programme ausführen lassen.

Ergänzend wird die Datei build.gradle mitgeliefert, die den Ablauf des Builds für Gradle beschreibt. Dieses Build-Tool besitzt viele Vorzüge, wie die kompakte und gut lesbare Notation, und vereinfacht die Verwaltung von Abhängigkeiten enorm. Darüber hinaus erlaubt Gradle das Starten von Programmen, wobei der jeweilige Programmname in Kapitälchenschrift, etwa DATETIMEEXAMPLE, angegeben wird.

Blockkommentare in ListingsBeachten Sie bitte, dass sich in den Listings diverse Blockkommentare finden, die der Orientierung und dem besseren Verständnis dienen. In der Praxis sollte man derartige Kommentierungen mit Bedacht einsetzen und lieber einzelne Sourcecode-Abschnitte in Methoden auslagern. Für die Beispiele des Buchs dienen diese Kommentare aber als Anhaltspunkte, weil die eingeführten oder dargestellten Sachverhalte für Sie als Leser vermutlich noch neu und ungewohnt sind.

public static void main(final String[] args) throws InterruptedException,

IOException

{

// Prozess erzeugen

final String command = "sleep 60s";

final String commandWin = "cmd timeout 60";

final Process sleeper = Runtime.getRuntime().exec(command);

...

// Process => ProcessHandle

final ProcessHandle sleeperHandle = ProcessHandle.of(sleeper.pid()).

orElseThrow(IllegalStateException::new);

...

}

Konventionen

Verwendete Zeichensätze

In diesem Buch gelten folgende Konventionen bezüglich der Schriftart: Neben der vorliegenden Schriftart werden wichtige Textpassagen kursiv oder kursiv und fett markiert. Englische Fachbegriffe werden eingedeutscht großgeschrieben, etwa Event Handling. Zusammensetzungen aus englischen und deutschen (oder eingedeutschten) Begriffen werden mit Bindestrich verbunden, z. B. Plugin-Manager. Namen von Programmen und Entwurfsmustern werden in KAPITÄLCHEN geschrieben. Listings mit Sourcecode sind in der Schrift Courier gesetzt, um zu verdeutlichen, dass dies einen Ausschnitt aus einem Java-Programm darstellt. Auch im normalen Text wird für Klassen, Methoden, Konstanten und Parameter diese Schriftart genutzt.

Tipps und Hinweise aus der Praxis

Dieses Buch ist mit diversen Praxistipps gespickt. In diesen werden interessante Hintergrundinformationen präsentiert oder es wird auf Fallstricke hingewiesen.

Tipp: Praxistipp

In derart formatierten Kästen finden sich im späteren Verlauf des Buchs immer wieder einige wissenswerte Tipps und ergänzende Hinweise zum eigentlichen Text.

Verwendete Klassen aus dem JDK

Werden Klassen des JDKs erstmalig im Text erwähnt, so wird deren voll qualifizierter Name, d. h. inklusive der Package-Struktur, angegeben: Die Klasse String würde demnach als java.lang.String notiert – alle weiteren Nennungen erfolgen dann ohne Angabe des Package-Namens. Diese Regelung erleichtert initial die Orientierung und ein Auffinden im JDK und zudem wird der nachfolgende Text nicht zu sehr aufgebläht. Die voll qualifizierte Angabe hilft insbesondere, da in den Listings eher selten import-Anweisungen abgebildet werden.

Im Text beschriebene Methodenaufrufe enthalten in der Regel die Typen der Übergabeparameter, etwa substring(int, int). Sind die Parameter in einem Kontext nicht entscheidend, wird mitunter auf deren Angabe aus Gründen der besseren Lesbarkeit verzichtet – das gilt ganz besonders für Methoden mit generischen Parametern.

Verwendete Abkürzungen

Im Buch verwende ich die in der nachfolgenden Tabelle aufgelisteten Abkürzungen. Weitere Abkürzungen werden im laufenden Text in Klammern nach ihrer ersten Definition aufgeführt und anschließend bei Bedarf genutzt.

Abkürzung

Bedeutung

API

Application Programming Interface

ASCII

American Standard Code for Information Interchange

(G)UI

(Graphical) User Interface

IDE

Integrated Development Environment

JDK

Java Development Kit

JEP

JDK Enhancement Proposal

JLS

Java Language Specification

JRE

Java Runtime Environment

JSR

Java Specification Request

JVM

Java Virtual Machine

Danksagung

Ein Fachbuch zu schreiben ist eine schöne, aber arbeitsreiche und langwierige Aufgabe. Alleine kann man dies kaum bewältigen. Daher möchte ich mich an dieser Stelle bei allen bedanken, die direkt oder indirekt zum Gelingen des Buchs beigetragen haben. Insbesondere konnte ich bei der Erstellung des Manuskripts auf ein starkes Team an Korrekturlesern zurückgreifen. Es ist hilfreich, von den unterschiedlichen Sichtweisen und Erfahrungen profitieren zu dürfen.

Zunächst einmal möchte ich mich bei Michael Kulla, der als Trainer für Java SE und Java EE bekannt ist, für sein mehrmaliges, gründliches Review vieler Kapitel und die fundierten Anmerkungen bedanken. Ebenfalls bin ich Prof. Dr. Dominik Gruntz sehr dankbar für diverse Verbesserungsvorschläge in den Kapiteln zu Java 12 und 14. Zudem erhielt ich den einen oder anderen Tipp von Jean-Claude Brantschen sowie noch in letzter Minute von Prof. Dr. Carsten Kern.

Nachfolgende Danksagung bezieht sich auf den Java-9-Teil sowie die Texte zur Modularisierung. Merten Driemeyer, Dr. Clemens Gugenberger, Prof. Dr. Carsten Kern sowie Andreas Schöneck haben mit verschiedenen hilfreichen Anmerkungen zu einer Verbesserung beigetragen. Zudem hat Ralph Willenborg dort mal wieder ganz genau gelesen und so diverse Tippfehler gefunden. Vielen Dank dafür! Auch von Albrecht Ermgassen erhielt ich den einen oder anderen Hinweis.

Schließlich bedanke ich mich bei einigen ehemaligen Arbeitskollegen der Zühlke Engineering AG: Jeton Memeti und Marius Reusch trugen durch ihre Kommentare zur Klarheit und Präzisierung bei. Auch von Hermann Schnyder von der Swisscom erhielt ich ein paar Anregungen.

Ebenso geht ein Dankeschön an das Team des dpunkt.verlags (Dr. Michael Barabas, Martin Wohlrab, Anja Weimer und Stefanie Weidner) für die tolle Zusammenarbeit. Außerdem möchte ich mich bei Torsten Horn für die fundierte fachliche Durchsicht sowie bei Ursula Zimpfer für ihre Adleraugen beim Copy-Editing bedanken.

Abschließend geht ein lieber Dank an meine Frau Lilija für ihr Verständnis und die Unterstützung. Glücklicherweise musste sie beim Entstehen dieses Buchs zu den Neuerungen in Java 9 bis 14 einen weit weniger gestressten Autor ertragen, als dies früher beim Schreiben meines Buchs »Der Weg zum Java-Profi« der Fall war.

Anregungen und Kritik

Trotz großer Sorgfalt und mehrfachen Korrekturlesens lassen sich missverständliche Formulierungen oder sogar Fehler leider nicht vollständig ausschließen. Falls Ihnen etwas Derartiges auffallen sollte, so zögern Sie bitte nicht, mir dies mitzuteilen. Gerne nehme ich auch Anregungen oder Verbesserungsvorschläge entgegen. Kontaktieren Sie mich bitte per Mail unter:

michael_inden@hotmail.com

Zürich, im März 2020
Michael Inden

1Einleitung

Früher wurden Java-Releases aufgrund unfertiger Features häufiger verschoben. Um dem entgegenzuwirken, hat Oracle nach dem Erscheinen von Java 9 auf einen halbjährlichen Releasezyklus umgestellt. Das erlaubt es, die jeweils bis zu diesem Zeitpunkt fertig implementierten Funktionalitäten zu veröffentlichen. Zwar kann diese schnelle Releasefolge eine größere Herausforderung für Toolhersteller sein, für uns als Entwickler ist es aber oftmals positiv, weil wir potenziell weniger lang auf neue Features warten müssen. Das konnte früher recht mühsam sein, wie die letzten Jahre gezeigt haben. Ein paar Gedanken dazu greift der nachfolgende Hinweiskasten auf.

Allerdings gilt das Positive vor allem für eigene Hobbyprojekte, weil man dort mit den Neuerungen experimentieren kann und weniger durch Restriktionen eingeschränkt ist. Im professionellen Einsatz wird man eher auf Kontinuität und die Verfügbarkeit von Security Updates setzen, weshalb in diesem Kontext vermutlich nur LTS-Versionen in Betracht kommen, um Migrationsaufwände kalkulierbar und zeitlich besser planbar zu halten.

Hinweis: Oracles neue Releasepolitik

Bis einschließlich Java 9 wurden neue Java-Versionen immer Feature-basiert veröffentlicht. Das hatte in der Vergangenheit oftmals und mitunter auch beträchtliche Verschiebungen des geplanten Releasetermins zur Folge, wenn für die Version wesentliche Features noch nicht fertig waren. Insbesondere deshalb verzögerten sich Java 8 und Java 9 um mehrere Monate bzw. sogar über ein Jahr: Rund 3,5 Jahre nach dem Erscheinen von JDK 8 im März 2014 ging Java mit Version 9 im September 2017 an den Start. Wieder einmal musste die Java-Gemeinde auf die Veröffentlichung der Version 9 des JDKs länger warten – es gab gleich mehrere Verschiebungen, zunächst von September 2016 auf März 2017, dann auf Juli 2017 und schließlich auf September 2017.

Mit einer zeitbasierten Releasestrategie möchte man derartigen Verzögerungen entgegenwirken, indem jedes halbe Jahr eine neue Java-Version veröffentlicht wird, die all jene Features enthält, die bereits fertig sind. Alle drei Jahre ist dann eine LTS-Version (Long Term Support) geplant. Eine solche ist in etwa vergleichbar mit den früheren Major-Versionen.

Was erwartet Sie im Folgenden?

Dieses Buch gibt einen fundierten Überblick über diverse wesentliche Erweiterungen in den JDKs 9 bis 14. Es werden unter anderem folgende Themen behandelt:

API- und SyntaxerweiterungenWir schauen uns verschiedene Änderungen an der Syntax von Java an. Neben Erweiterungen bei der @Deprecated-Annotation widmen wir uns Details zu Bezeichnern, dem Diamond Operator und vor allem gehe ich kritisch auf das Feature privater Methoden in Interfaces ein. Für Java 10 und 11 thematisiere ich die Syntaxerweiterung var als Möglichkeit zur Definition lokaler Variablen bzw. zur Verwendung in Lambdas. Im Kontext von instanceof ist es mit Java 14 möglich, künstliche Hilfsvariablen und unschöne Casts zu vermeiden.

Kommen wir zu den APIs: In Java 9 wurden diverse APIs ergänzt oder neu eingeführt. Auch Bestehendes, wie z. B. das Stream-API oder die Klasse Optional<T>, wurde um Funktionalität erweitert. Neben Vereinfachungen beim Prozess-Handling, der Verarbeitung mit Optional<T> oder von Daten mit InputStreams schauen wir auf fundamentale Neuerungen im Bereich der Concurrency durch Reactive Streams. Darüber hinaus enthalten Java 10 und 11 eine Vielzahl kleinerer weiterer Neuerungen. Eine größere Änderung ist der mit Java 11 offiziell ins JDK 11 aufgenommene HTTP/2-Support. In Java 12 wurden ein paar API-Erweiterungen integriert. Mit Java 13 finden wir zwei Previews auf Syntaxänderungen, nämlich einmal bezüglich switch und zudem die sogenannten »Text Blocks«, die mehrzeilige Strings erlauben. Mit Java 14 werden die Neuerungen bei switch endlich in den Sprachstandard aufgenommen. Außerdem finden wir eine hilfreiche Neuerung zur Fehleranalyse bei NullPointerExceptions. Zusätzlich bietet Java 14 zwei Erweiterungen bei der Definition mehrzeiliger Texte. Ganz besonders interessant sind sogenannte Records, die eine extrem kompakte Schreibweise zum Deklarieren spezieller Klassen mit unveränderlichen Daten ermöglichen.

JVM-ÄnderungenIn jeweils eigenen Abschnitten beschäftigen wir uns mit Änderungen in der JVM, für JDK 9 etwa in Bezug auf die Nummerierung von Java-Versionen oder javadoc. Zudem kann für Quereinsteiger und Neulinge die durch das Tool jshell bereitgestellte Java-Konsole mit REPL-Unterstützung (Read-Eval-Print-Loop) erste Experimente und Gehversuche erleichtern, ohne dafür den Compiler oder eine IDE bemühen zu müssen. Mit Java 11 kommt ein neuer Garbage Collector sowie mit dem Feature »Launch Single-File Source-Code Programs« die Möglichkeit, Java-Klassen ohne explizite vorherige Kompilierung ausführen zu lassen und somit für Scripting einsetzen zu können. Java 12 bietet als wesentliche Neuerung die Integration des Microbenchmark-Frameworks JMH (Java Microbenchmarking Harness).

ModularisierungDie Modularisierung adressiert zwei typische Probleme größerer Java-Applikationen. Zum einen ist dies die sogenannte JAR-Hell, womit gemeint ist, dass sich im CLASSPATH verschiedene JARs mit zum Teil inhaltlichen Überschneidungen (unterschiedliche Versionen mit Abweichungen in Packages oder gleiche Klassen, jedoch mit anderem Bytecode) befinden. Dabei kann allerdings nicht sichergestellt werden, wann welche Klasse aus welchem JAR eingebunden wird. Zum anderen sind als public definierte Typen beliebig von anderen Packages aus zugreifbar. Das erschwert die Kapselung. Mit JDK 9 lassen sich eigenständige Softwarekomponenten (Module) mit einer Sichtbarkeitssteuerung definieren. Das hat allerdings weitreichende Konsequenzen: Sofern man Module verwendet, kann man Programme mit JDK 9 nicht mehr ohne Weiteres wie gewohnt starten, wenn diese externe Abhängigkeiten besitzen. Das liegt vor allem daran, dass Abhängigkeiten nun beim Programmstart geprüft und dazu explizit beschrieben werden müssen.

Es gibt aber einen rein auf dem CLASSPATH basierenden Kompatibilitätsmodus, der ein Arbeiten wie bis einschließlich JDK 8 gewohnt ermöglicht.

Tipp: Beispiele und der Kompatibilitätsmodus

Zum Ausprobieren verschiedener Neuerungen aus JDK 9 bis 14 werden wir kleine Beispielapplikationen in main()-Methoden erstellen. Dabei ist es für erste Experimente und für die Migration bestehender Anwendungen von großem Vorteil, dass man das an sich modularisierte JDK auch ohne eigene Module und ihre Sichtbarkeitsbeschränkungen betreiben kann. In diesem Kompatibilitätsmodus wird wie zuvor bei Java 8 mit .class-Dateien, JARs und dem CLASSPATH gearbeitet. Für zukünftige Projekte wird man mitunter Module nutzen wollen. Das schauen wir uns in eigenen Kapiteln an.

Ausprobieren der Java-14-Beispiele

Durch die kurzen Releasezyklen werden mittlerweile einige Features als Previews der Entwicklergemeinde vorgestellt. Möchte man diese nutzen, so sind in den IDEs und Build-Tools gewisse Parametrierungen sowohl beim Kompilieren als auch beim Ausführen nötig, etwa wie im folgenden Beispiel:

java --enable-preview -cp build/libs/Java14Examples.jar \

java14.RecordExamples

Details dazu werden in Abschnitt 9.4 beschrieben.

Entdeckungsreise JDK 9 bis 14 – Wünsche an die Leser

Ich wünsche allen Lesern viel Freude mit diesem Buch sowie einige neue Erkenntnisse und viel Spaß beim Experimentieren mit JDK 9 bis 14. Möge Ihnen der Umstieg auf die von Ihnen bevorzugte Java-Version und die Erstellung modularer Applikationen oder die Migration bestehender Anwendungen durch die Lektüre meines Buchs leichter fallen.

Wenn Sie zunächst eine Auffrischung Ihres Wissens zu Java 8 und seinen Neuerungen benötigen, bietet sich ein Blick in den Anhang A an.

Teil I

Neuerungen in Java 9 bis 11

2Syntaxerweiterungen in JDK 9 bis 11

In JDK 9 finden sich verschiedene kleinere Syntaxerweiterungen, die wir uns hier anschauen. Außerdem lernen wir mit var die sogenannte Local Variable Type Inference als Syntaxerweiterung in Java 10 kennen, die mit Java 11 leicht erweitert wurde.

2.1Anonyme innere Klassen und der Diamond Operator

Bei der Definition anonymer innerer Klassen konnte man den Diamond Operator bis JDK 8 leider nicht nutzen, sondern der Typ aus der Deklaration war auch bei der Definition explizit anzugeben. Praktischerweise ist es mit JDK 9 (endlich) möglich, auf diese redundante Typangabe zu verzichten. Als Beispiel dient die Definition eines Komparators mit dem Interface java.util.Comparator<T>.

Beispiel mit JDK 8

Bis JDK 8 musste man bei der Definition einer anonymen inneren Klasse den Typ noch wie folgt angeben:

final Comparator<String> byLengthJdk8 = new Comparator<String>()

{

...

};

Beispiel mit JDK 9

Die Änderung zu JDK 8 ist kaum sichtbar: Mit JDK 9 ist es nun erlaubt, die Typangabe wegzulassen und somit den Diamond Operator zu verwenden, wie wir dies von anderen Variablendefinitionen bereits gewohnt sind:

final Comparator<String> byLength = new Comparator<>()

{

...

};

Für Komparatoren gibt es seit JDK 8 zwei Neuerungen, die die Definition erleichtern. Im Anhang A behandle ich unter anderem auch die Erweiterungen bei Komparatoren.

2.2Erweiterung der @Deprecated-Annotation

Die @Deprecated-Annotation dient bekanntlich zum Markieren von obsoletem Sourcecode und besaß bislang keine Parameter. Das ändert sich mit JDK 9: Die @Deprecated-Annotation wurde um die zwei Parameter since und forRemoval erweitert. Die Annotation ist nun im JDK wie folgt definiert:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER,

TYPE})

public @interface Deprecated {

/**

* Returns the version in which the annotated element became deprecated.

* The version string is in the same format and namespace as the value of

* the {@code @since} javadoc tag. The default value is the empty

* string.

*

* @return the version string

* @since 9

*/

String since() default "";

/**

* Indicates whether the annotated element is subject to removal in a

* future version. The default value is {@code false}.

*

* @return whether the element is subject to removal

* @since 9

*/

boolean forRemoval() default false;

}

Diese Erweiterung wurde nötig, weil für Folgereleases von Java 9 geplant ist, veraltete Funktionalität aus dem JDK zu entfernen, statt sie – wie bislang für Java üblich – aus Gründen der Rückwärtskompatibilität ewig beizubehalten. Das folgende Beispiel zeigt eine Anwendung, wie sie aus dem JDK stammen könnte:

@Deprecated(since = "1.5", forRemoval = true)

Mithilfe der beiden Parameter kann man für veralteten Sourcecode angeben, in welcher Version (since) dieser mit der Markierung als @Deprecated versehen wurde und ob der Wunsch besteht, die markierten Sourcecode-Teile in zukünftigen Versionen zu entfernen (forRemoval). Weil beide Parameter Defaultwerte besitzen (since = "" und forRemoval = false), können die Angaben jeweils für sich alleine stehen oder ganz entfallen.

Diese Erweiterung der @Deprecated-Annotation kann man selbstverständlich auch für eigenen Sourcecode nutzen und so anzeigen, dass gewisse Funktionalitäten für die Zukunft nicht mehr angeboten werden sollen. Darüber hinaus empfiehlt es sich, in einem Javadoc-Kommentar das @deprecated-Tag zu verwenden und dort den Grund der Deprecation und eine empfohlene Alternative aufzuführen. Nachfolgend ist dies exemplarisch für eine veraltete Methode someOldMethod() gezeigt:

/**

* @deprecated this method is replaced by someNewMethod()

* ({@link #someNewMethod()}) which is more stable

*/

@Deprecated(since = "7.2", forRemoval = true)

private static void someOldMethod()

{

// ...

}

2.3Private Methoden in Interfaces

Allgemein bekannt ist, dass Interfaces der Definition von Schnittstellen dienen. Leider verlieren in Java die Interfaces immer mehr von ihrer eigentlichen Bedeutung: Seit Java 8 sind statische Methoden und Defaultmethoden in Interfaces erlaubt. Mit beiden kann man Implementierungen vorgeben.1 Dadurch unterscheiden sich Interfaces kaum mehr von einer abstrakten Klasse: Letztere können ergänzend einen Zustand in Form von Attributen besitzen, was in Interfaces (noch) nicht geht.

Mit JDK 9 wurde der Unterschied zwischen Interfaces und abstrakten Klassen nochmals verringert, weil sich nun auch private Methoden in Interfaces definieren lassen. Das Argument dafür war, dass sich damit die Duplikation von Sourcecode in Defaultmethoden reduzieren ließe. Das mag richtig sein. Allerdings ist es für die meisten Anwendungsprogrammierer eher fraglich, ob diese jemals Defaultmethoden selbst implementieren sollten. Für Framework-Entwickler können private Methoden in Interfaces eventuell von Nutzen sein.

Beispiel

Schauen wir uns zur Demonstration privater Methoden in Interfaces das nachfolgende Listing und vor allem die private Methode myPrivateCalcSum(int, int) sowie deren Aufruf aus den beiden öffentlichen Defaultmethoden an:

public interface PrivateMethodsExample

{

// Tatsächliche Schnittstellendefinition - public abstract ist optional

public abstract int method1();

public abstract String method2();

public default int sum(final String num1, final String num2)

{

final int value1 = Integer.parseInt(num1);

final int value2 = Integer.parseInt(num2);

return myPrivateCalcSum(value1, value2);

}

public default int sum(final int value1, final int value2)

{

return myPrivateCalcSum(value1, value2);

}

// Neu und unschön in JDK 9

private int myPrivateCalcSum(final int value1, final int value2)

{

return value1 + value2;

}

}

Kommentar

Vielleicht fragen Sie sich, warum ich den privaten Methoden in Interfaces so ablehnend gegenüberstehe. Tatsächlich wurde die Büchse der Pandora bereits mit JDK 8 und den Defaultmethoden geöffnet. Die privaten Methoden mögen für Framework-Entwickler mitunter praktisch sein, jedoch besteht die Gefahr, dass sie für »normale« Entwickler noch attraktiver werden und von diesen somit ohne großes Hinterfragen zur Applikationsentwicklung eingesetzt werden. Das wäre aber im Hinblick auf das Design und die Klarheit von Business-Applikationen ein Schritt in die falsche Richtung.2 Dadurch wird unter Umständen dem Schnittstellenentwurf weniger Aufmerksamkeit gewidmet, basierend auf der Annahme, dass benötigte Funktionalität immer noch nachträglich hinzugefügt werden kann.

2.4Verbotener Bezeichner ’_

Bei den Bezeichnern gibt es eine kleine Änderung: Der Compiler erlaubt mit JDK 9 das Zeichen _ (Unterstrich) nicht mehr als Bezeichner.

Während folgende Zeile mit JDK 8 noch kompilierte

final String _ = "Underline";

produziert der Java-Compiler mit JDK 9 folgende Fehlermeldung:

as of release 9, ’_’ is a keyword, and may not be used as an identifier

Ich persönlich halte ein einzelnes Zeichen als Variablenbezeichner fast immer für einen Bad Smell und insbesondere gilt dies für den Unterstrich. Vermutlich sehen Sie dies ähnlich. Insofern stellt diese Änderung wohl eher selten ein Problem dar.

2.5Syntaxerweiterung var (JDK 10 und 11)

Wie einleitend erwähnt, bietet Java 10 die Local Variable Type Inference als Syntaxerweiterung. Diese erlaubt es, auf die explizite Typangabe auf der linken Seite einer Variablendefinition zu verzichten, sofern sich der konkrete Typ für eine lokale Variable anhand der Definition auf der rechten Seite der Zuweisung vom Compiler ermitteln lässt.

Einführende Beispiele

Schauen wir uns einige einführende Beispiele für die Kurzschreibweise mit var für Variablendefinitionen an:

var name = "Peter"; // var => String

var chars = name.toCharArray(); // var => char[]

var mike = new Person("Mike", 47); // var => Person

var hash = mike.hashCode(); // var => int

Insbesondere im Zusammenhang mit generischen Containern spielt die Local Variable Type Inference ihre Vorteile aus:

// var => ArrayList<String>

var names = new ArrayList<String>();

names.add("Tim");

names.add("Tom");

names.add("Jerry");

// var => Map<String, Long>

var personAgeMapping = Map.of("Tim", 47L, "Tom", 12L,

"Michael", 47L, "Max", 25L);

Vor allem wenn die Typangaben mehrere generische Parameter umfassen, kann var den Sourcecode deutlich kürzer und mitunter lesbarer machen. Betrachten wir als Beispiel eine Verschachtelung von Typen analog zu den folgenden:

In solchen Fällen spart var einiges an Schreibarbeit – zusätzlich erfolgt hier noch ein statischer Import verschiedener Kollektoren, um die Lesbarkeit zu steigern:

// var => Set<Map.Entry<String, Long»

var entries = personAgeMapping.entrySet();

// var => Map<Character, Set<Map.Entry<String, Long>>>

var filteredPersons = personAgeMapping.entrySet().stream().

collect(groupingBy(firstChar,

filtering(isAdult, toSet())));

Im Beispiel werden zur Beschreibung der Gruppierung und zur Filterung folgende zwei Lambdas genutzt, worauf ich gleich nochmals genauer eingehe:

Function<Map.Entry<String, Long>, Character> firstChar =

entry -> entry.getKey().charAt(0);

Predicate<Map.Entry<String, Long>> isAdult = entry -> entry.getValue() >= 18;

Hilfestellung in IDEs

So angenehm die Kurzschreibweise in der Regel auch ist, so wünschenswert ist manchmal eine Expansion in oder ein Hinweis auf den konkreten Typ. In beiden Fällen ist der intelligente Tooltip in Eclipse hilfreich, wie folgende Abbildung 2-1 zeigt.

Abbildung 2-1 Hilfestellung zu var in Eclipse

Vermutlich eher selten besteht das Bedürfnis, doch wieder den konkreten Typ statt var zu nutzen. Praktischerweise existieren Quick Fixes in den gebräuchlichen IDEs, um zwischen konkretem Typ und var leicht hin- und herzuwechseln.

Lambda-Ausdrücke und var

Eine Kleinigkeit sollten wir noch betrachten: Im vorherigen Beispiel kommen beim Aufbereiten der Map folgende zwei Lambdas zum Einsatz, um die Funktionalität zu realisieren:

Function<Map.Entry<String, Long>, Character> firstChar =

entry -> entry.getKey().charAt(0);

Predicate<Map.Entry<String, Long>> isAdult = entry -> entry.getValue() >= 18;

Wäre es nicht wünschenswert, auch hier die Typangabe mit var abzukürzen? Eigentlich ja! Warum dies nicht geht, erkläre ich im Folgenden.

Der Compiler kann allein auf Basis dieser Lambdas den konkreten Typ nicht ermitteln. Somit ist keine Umwandlung in var möglich, sondern führt zur Fehlermeldung »lambda expression needs an explicit target-type«. Wollte man var trotzdem nutzen, so müsste man folgenden Cast einfügen:

var isAdultVar =

(Predicate<Map.Entry<String, Long>>) entry -> entry.getValue() >= 18;

Insgesamt sieht man, dass var für Lambda-Ausdrücke eher ungeeignet ist. Das ist insofern schade, weil hier einige Schreibarbeit gespart werden könnte.

Beschränkungen

Rekapitulieren wir kurz: var ist für lokale Variablen gedacht, die direkt initialisiert werden. Damit ist es im Speziellen auch für Variablen in for-Schleifen und try-with-resources geeignet. Darüber hinaus scheint mitunter wünschenswert, var