150 37 10MB
German Pages 344 Year 2019
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 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 12 Modularisierung, Syntax- und API-Erweiterungen
Michael Inden [email protected] Lektorat: Dr. Michael Barabas Copy-Editing: Ursula Zimpfer, Herrenberg Satz: Michael Inden Herstellung: Stefanie Weidner Umschlaggestaltung: Helmut Kraus, www.exclam.de Druck und Bindung: mediaprint solutions GmbH, 33100 Paderborn Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. ISBN: Print PDF ePub mobi
978-3-86490-672-5 978-3-96088-777-5 978-3-96088-778-2 978-3-96088-779-9
1. Auflage 2019 Copyright © 2019 dpunkt.verlag GmbH Wieblinger Weg 17 69123 Heidelberg Hinweis: Dieses Buch wurde auf PEFC-zertifiziertem Papier aus nachhaltiger Waldwirtschaft gedruckt. Der Umwelt zuliebe verzichten wir zusätzlich auf die Einschweißfolie.
Das Papier für dieses Buch stammt aus nachhaltig bewirtschafteten Wäldern und kontrollierten Quellen. www.pefc.de
Schreiben Sie uns: Falls Sie Anregungen, Wünsche und Kommentare haben, lassen Sie es uns wissen: [email protected]. 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. Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und Produktbezeichnungen der jeweiligen Firmen im Allgemeinen warenzeichen-, marken- oder patentrechtlichem Schutz unterliegen. Alle Angaben und Programme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch für Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen. 543210
v
Inhaltsverzeichnis
1
I 2 2.1 2.2 2.3 2.4
Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Sprach- und API-Erweiterungen in Java 9
1
5
Syntaxerweiterungen in JDK 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anonyme innere Klassen und der Diamond Operator . . . . . . . . . . . . . Erweiterung der @Deprecated-Annotation . . . . . . . . . . . . . . . . . . . . . . Private Methoden in Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verbotener Bezeichner ’_’ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7 7 8 9 11
3 Neues und Änderungen in JDK 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1 Neue und erweiterte APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.1 Das neue Process-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.2 Collection-Factory-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.3 Reactive Streams und die Klasse Flow . . . . . . . . . . . . . . . . . . . 3.1.4 Erweiterungen in der Klasse InputStream . . . . . . . . . . . . . . . 3.1.5 Erweiterungen rund um die Klasse Optional . . . . . . . . . 3.1.6 Erweiterungen im Stream-API . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.1.7 Erweiterungen in der Klasse LocalDate . . . . . . . . . . . . . . . . . 3.1.8 Erweiterungen in der Klasse Arrays . . . . . . . . . . . . . . . . . . . . . 3.1.9 Erweiterungen in der Klasse Objects . . . . . . . . . . . . . . . . . . . . 3.1.10 Erweiterungen in der Klasse CompletableFuture . . . . 3.2 Sonstige Änderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Optimierung bei Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Deprecation diverser Typen und Methoden im JDK . . . . . . . . .
13 13 13 19 23 33 35 40 44 45 47 48 52 52 53
4 4.1 4.2 4.3 4.4
Änderungen in der JVM in JDK 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Änderung des Versionsschemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unterstützung von Multi-Release-JARs . . . . . . . . . . . . . . . . . . . . . . . . . . Java + REPL => jshell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HTML5 Javadoc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55 55 57 60 65
5
Übungen zu den Neuerungen in JDK 9 . . . . . . . . . . . . . . . . . . . . . . . .
67
vi
Inhaltsverzeichnis
II Sprach- und API-Erweiterungen in Java 10 bis 12
75
6 Neues und Änderungen in Java 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Syntaxerweiterung var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 API-Neuerungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2.1 Unveränderliche Kopien von Collections . . . . . . . . . . . . . . . . . . 6.2.2 Immutable Collections aus Streams erzeugen . . . . . . . . . . . . . 6.2.3 Erweiterung in der Klasse Optional . . . . . . . . . . . . . . . . . . . . . 6.2.4 Modifikationen in der Versionierung . . . . . . . . . . . . . . . . . . . . . . 6.2.5 Verschiedenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77 77 81 81 83 84 85 87 88
7 Neues und Änderungen in Java 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 Syntaxerweiterung für var . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 API-Neuerungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 Neue Hilfsmethoden in der Klasse String . . . . . . . . . . . . . . . . 7.2.2 Neue Hilfsmethoden in der Utility-Klasse Files . . . . . . . . . . . 7.2.3 Erweiterung in der Klasse Optional . . . . . . . . . . . . . . . . . 7.2.4 Erweiterung im Interface Predicate . . . . . . . . . . . . . . . . . 7.2.5 HTTP/2-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Neuerungen in der JVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3.1 Epsilon Garbage Collector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3.2 Launch Single-File Source-Code Programs . . . . . . . . . . . . . . . . 7.3.3 Das Tool Flight Recorder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Deprecations und Entfernungen im JDK . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.1 Aufräumarbeiten in der Klasse Thread . . . . . . . . . . . . . . . . . . . . 7.4.2 Deprecation der JavaScript-Unterstützung . . . . . . . . . . . . . . . . . 7.4.3 Ausgliederung von JavaFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4.4 Ausgliederung von Java EE und CORBA . . . . . . . . . . . . . . . . . . 7.5 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89 90 91 91 93 95 95 96 101 101 101 102 102 102 102 103 103 104
8 Neues und Änderungen in Java 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1 Switch Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.1 Einführendes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.2 Zuweisungen im Lambda . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.3 break mit Rückgabewert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2 Microbenchmark Suite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2.1 Eigene Microbenchmarks und Varianten davon . . . . . . . . . . . . 8.2.2 Microbenchmarks mit JMH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.2.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105 105 105 109 109 110 111 113 118
Inhaltsverzeichnis 8.3 Java 12 – notwendige Anpassungen für Build-Tools und IDEs . . . . . . 8.3.1 Java 12 mit Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3.2 Java 12 mit Maven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3.3 Java 12 mit Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.3.4 Java 12 mit IntelliJ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.4 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
vii 119 119 120 121 121 121
Übungen zu den Neuerungen in den JDKs 10 und 11 . . . . . . . . . . 123
III Modularisierung
131
10 Modularisierung mit Project Jigsaw . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1.1 Bisherige Varianten der Modularisierung . . . . . . . . . . . . . . . . . . 10.1.2 Warum Modularisierung wünschenswert ist . . . . . . . . . . . . . . . . 10.2 Modularisierung im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.1 Grundlagen zu Project Jigsaw . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.2 Einführendes Beispiel mit zwei Modulen . . . . . . . . . . . . . . . . . . 10.2.3 Packaging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.4 Linking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.5 Abhängigkeiten und Modulgraphen . . . . . . . . . . . . . . . . . . . . . . . 10.2.6 Module des JDKs einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2.7 Arten von Modulen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3 Sichtbarkeiten und Zugriffsschutz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.1 Sichtbarkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.2 Zugriffsschutz an Beispielen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3.3 Transitive Abhängigkeiten (Implied Readability) . . . . . . . . . . . . 10.4 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
133 134 135 137 138 138 146 155 157 161 163 168 170 170 172 177 182
11 Weiterführende Themen zur Modularisierung . . . . . . . . . . . . . . . . . 11.1 Empfehlenswertes Verzeichnislayout für Module . . . . . . . . . . . . . . . . . . 11.2 Modularisierung und Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.1 Begrifflichkeiten: API, SPI und Service Provider . . . . . . . . . . . . 11.2.2 Service-Ansatz in Java seit JDK 6 . . . . . . . . . . . . . . . . . . . . . . . . 11.2.3 Services im Bereich der Modularisierung . . . . . . . . . . . . . . . . . . 11.2.4 Definition eines Service Interface . . . . . . . . . . . . . . . . . . . . . . . . 11.2.5 Realisierung eines Service Provider . . . . . . . . . . . . . . . . . . . . . . 11.2.6 Realisierung eines Service Consumer . . . . . . . . . . . . . . . . . . . . 11.2.7 Kontrolle der Abhängigkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2.8 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
183 184 186 186 187 190 191 193 194 197 198
viii
Inhaltsverzeichnis
11.3 Modularisierung und Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.3.1 Verarbeitung von Modulen mit Reflection . . . . . . . . . . . . . . . . . . 11.3.2 Tool zur Ermittlung von Modulen zu Klassen . . . . . . . . . . . . . . . 11.3.3 Besonderheiten bei Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4 Kompatibilität und Migration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.1 Kompatibilitätsmodus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.2 Migrationsszenarien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4.3 Fallstrick bei der Bottom-up-Migration . . . . . . . . . . . . . . . . . . . . . 11.4.4 Beispiel: Migration mit Automatic Modules . . . . . . . . . . . . . . . . . 11.4.5 Beispiel: Automatic und Unnamed Module . . . . . . . . . . . . . . . . . 11.4.6 Beispiel: Abwandlung mit zwei Automatic Modules . . . . . . . . . 11.4.7 Mögliche Schwierigkeiten bei Migrationen . . . . . . . . . . . . . . . . . 11.4.8 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
IV
Übungen zur Modularisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Verschiedenes
13 Build-Tools und IDEs mit Java 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1 Nicht modularisierte Applikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1.1 Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1.2 Maven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1.3 Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1.4 IntelliJ IDEA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.1.5 Externe Abhängigkeiten im Kompatibilitätsmodus . . . . . . . . . . 13.2 Modularisierte Applikationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.1 Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.2 Maven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.3 Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.4 IntelliJ IDEA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3 Fazit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
199 199 201 203 209 209 212 216 218 219 222 224 224
235 237 237 239 241 243 243 244 246 247 251 256 258 260
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
Inhaltsverzeichnis
V
Anhang
A Schnelleinstieg in Java 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.1 Einstieg in Lambdas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.1.1 Lambdas am Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.1.2 Functional Interfaces und SAM-Typen . . . . . . . . . . . . . . . . . . . . A.1.3 Type Inference und Kurzformen der Syntax . . . . . . . . . . . . . . . . A.1.4 Methodenreferenzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.2 Streams im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.2.1 Streams erzeugen – Create Operations . . . . . . . . . . . . . . . . . . . A.2.2 Intermediate und Terminal Operations im Überblick . . . . . . . . . A.2.3 Zustandslose Intermediate Operations . . . . . . . . . . . . . . . . . . . . A.2.4 Zustandsbehaftete Intermediate Operations . . . . . . . . . . . . . . . A.2.5 Terminal Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3 Neuerungen in der Datumsverarbeitung . . . . . . . . . . . . . . . . . . . . . . . . . A.3.1 Die Klasse Instant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3.2 Die Klassen LocalDate, LocalTime und LocalDateTime A.3.3 Die Klasse Duration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3.4 Die Klasse Period . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3.5 Datumsarithmetik mit TemporalAdjusters . . . . . . . . . . . . . . A.4 Diverse Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.4.1 Erweiterungen im Interface Comparator . . . . . . . . . . . . . . A.4.2 Erweiterungen in der Klasse Optional . . . . . . . . . . . . . . . A.4.3 Erweiterungen in der Klasse CompletableFuture . . . .
ix
265 267 267 267 268 271 272 273 274 275 277 279 280 283 284 284 286 287 288 290 290 292 294
B Einführung Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 B.1 Projektstruktur für Maven und Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . 299 B.2 Builds mit Gradle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 301 C Einführung Maven . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 C.1 Maven im Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 C.2 Maven am Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
xi
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 12 und in den Vorgängern. Aufgrund des nun halbjährlichen Releasezyklus sind in den Java-Versionen 10, 11 und 12 jeweils weniger Änderungen als in früheren Releases enthalten. In diesem Buch werden 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. Eine weitreichende Neuerung von Java 9 ist sicherlich die Modularisierung, die es erlaubt, eigene Programme in kleinere Softwarekomponenten, sogenannte Module, zu unterteilen. Zudem wurde auch das JDK in Module aufgeteilt.
An wen richtet sich dieses Buch? Dieses Buch ist kein Buch für Programmierneulinge, sondern richtet sich an diejenigen Leser, die bereits solides Java-Know-how besitzen und sich nun kurz und prägnant über die wichtigsten Neuerungen in den Java-Versionen 9 bis 12 informieren wollen. Um die Beispiele des Buchs möglichst präzise und elegant zu halten, verwende ich diverse Features aus Java 8. Deshalb setzt der Text voraus, dass Sie sich schon mit den Neuerungen von Java 8 beschäftigt haben. Alle, die eine kleine Auffrischung benötigen, finden zum leichteren Einstieg im Anhang einen Crashkurs zu Java 8. Für einen fundierten Einstieg in Java 8 möchte ich Sie auf meine Bücher »Java 8 – Die Neuerungen« [2] oder alternativ »Der Weg zum Java-Profi« [4] verweisen. Zielgruppe Dieses Buch richtet 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 12 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.
xii
Vorwort
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 12 in eigenen Projekten erfolgreich gemeistert werden kann. Ich setze zwar ein gutes Java-Grundwissen voraus, allerdings werden ausgewählte Themengebiete etwas genauer und gegebenenfalls einführend betrachtet, wenn dies das Verständnis der nachfolgenden Inhalte erleichtert.
Aufbau dieses Buchs Nachdem Sie eine grobe Vorstellung über den Inhalt dieses Buchs haben, möchte ich die Themen der einzelnen Kapitel kurz vorstellen. Kapitel 1 – Einleitung Die Einleitung stimmt Sie auf Java 9 bis 12 ein und gibt einen groben Überblick, was Sie so alles in diesem Buch bzw. als Neuerungen erwartet. Kapitel 2 – Syntaxerweiterungen in JDK 9 Zunä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 neue Feature privater Methoden in Interfaces ein. Kapitel 3 – Neues und Änderungen in JDK 9 In 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 oder von Daten mit InputStreams schauen wir auf fundamentale Neuerungen im Bereich der Concurrency durch Reactive Streams. Kapitel 4 – Änderungen in der JVM in JDK 9 In 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 JavaVersionen finden wir in Java 9 Änderungen, die thematisiert werden. Kapitel 5 – Übungen zu den Neuerungen in JDK 9 In diesem Kapitel werden Übungsaufgaben zu den Themen der vorangegangenen Kapitel 2 bis 4 präsentiert. Deren Bearbeitung sollte Ihr Wissen zu den Neuerungen aus Java 9 vertiefen. Kapitel 6 – Neues und Änderungen in Java 10 Seit 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 die Syntaxerweiterung var zur Definition lokaler Variablen sowie einige APIErweiterungen vorgestellt.
Vorwort
xiii
Kapitel 7 – Neues und Änderungen in Java 11 Nachdem Java 9 und 10 jeweils nur 6 Monate aktuell waren, stellt Java 11 wieder ein über Jahre supportetes, sogenanntes LTS-Release dar, wobei LTS für Long Term Support steht. Neben 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 8 – Neues und Änderungen in Java 12 Zunächst waren zwei Syntaxerweiterungen für Java 12 vorgesehen. Leider die sogenannten »Raw String Literals« als Feature gestrichen, sodass für uns als Entwickler vor allem ein Preview auf Syntaxänderungen bezüglich switch verbleibt. Sofern Sie Optimierungen auf Mikroebene vornehmen mussten, um das letzte Quäntchen an Performance herauszuholen, interessiert es Sie bestimmt, dass das Microbenchmark-Framework JMH (Java Microbenchmarking Harness) ins JDK integriert wurde. Kapitel 9 – Übungen zu den Neuerungen in JDK 10 und 11 In diesem Kapitel werden Übungsaufgaben zu den Themen der vorangegangenen Kapitel 6 bis 8 präsentiert. Deren Bearbeitung sollte Ihr Wissen zu den Neuerungen aus Java 10 und 11 vertiefen – wobei keine Übungen zu Java 12 angeboten werden, da hier kaum relevante API-Erweiterungen stattgefunden haben. Kapitel 10 – Modularisierung mit Project Jigsaw Klar 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 auch Themen wie Sichtbarkeit und Zugriffsschutz behandelt. Kapitel 11 – Weiterführende Themen zur Modularisierung In diesem Kapitel schauen wir uns einige fortgeschrittenere Themen zur Modularisierung an. 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.
xiv
Vorwort
Kapitel 12 – Übungen zur Modularisierung Wie für die API-Erweiterungen werden auch für die Modularisierung verschiedene Übungsaufgaben in einem Kapitel zusammengestellt. Kapitel 13 – Build-Tools und IDEs mit Java 11 Während frühere Versionen von Java der Rückwärtskompatibilität viel Aufmerksamkeit geschenkt haben und sich dadurch die notwendigen Anpassungen in IDEs und Build-Tools in Grenzen hielten, führt Java 9 durch die Modularisierung zum ersten Mal zu einem größeren Bruch. Die neue Art und Weise, wie Module den Sourcecode strukturieren, wie Klassen geladen werden und wie Zugriffe eingeschränkt werden können, und vor allem das Verbot zum Zugriff auf interne Klassen des JDKs führen zu Inkompatibilitäten und erfordern einige Anstrengungen bei Toolherstellern. Zudem wird das JDK mittlerweile zum Teil nicht mehr rückwärtskompatibel weiter entwickelt: In JDK 11 wurden verschiedene Module aus dem JDK entfernt, wodurch externe Bibliotheken genutzt werden müssen, um etwa JAXB oder JavaFX einzubinden. Dieses Kapitel gibt einen Überblick über den derzeitigen Stand und betrachtet Java 11. Kapitel 14 – Zusammenfassung Dieses Kapitel fasst die Themen rund um die vielfältigen Neuerungen aus Java 9, 10, 11 und 12 noch einmal kurz zusammen. Anhang A – Schnelleinsteg in Java 8 In 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 Gradle Anhang B liefert eine kurze Einführung in das Build-Tool Gradle, mit dem die Beispiele dieses Buchs übersetzt wurden. Mit dem vermittelten Wissen sollten Sie dann auch kleinere eigene Projekte mit einem BuildSystem ausstatten können. Anhang C – Einführung Maven In 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
Vorwort
xv
www.dpunkt.de/java-die-neuerungen steht dann der vollständige, kompilierba-
re 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. Idealerweise nutzen Sie dazu Eclipse 2018-12 oder IntelliJ 2018.3. 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. Gradle erlaubt aber auch das Starten von Programmen, wobei der jeweilige Programmname in Kapitälchenschrift, etwa DATE T IME E XAMPLE, angegeben wird. Blockkommentare in Listings Beachten 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 K APITÄ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.
xvi
Vorwort
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
JLS
Java Language Specification
JRE
Java Runtime Environment
JSR
Java Specification Request
JVM
Java Virtual Machine
Vorwort
xvii
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. 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, Miriam Metsch und Birgit Bäuerlein) 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 12 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 sonstige Anregungen oder Verbesserungsvorschläge entgegen. Kontaktieren Sie mich bitte per Mail unter: [email protected] Zürich, im Februar 2019 Michael Inden
1
1
Einleitung
Während früher die Java-Releases aufgrund unfertiger Features häufig verschoben wurden, hat Oracle mit Java 10 auf halbjährliche Releases umgestellt, die jeweils die bis zu diesem Zeitpunkt fertig implementierten Features bereitstellen. Dadurch wurden sowohl Java 10 als auch Java 11 beide pünktlich im Abstand von rund 6 Monaten veröffentlicht und dies wird vermutlich auch für Java 12 gelten, dessen Releasetermin für den 19. März 2019 geplant ist. Während diese schnelle Releasefolge eine größere Herausforderung für Toolhersteller ist, kann dies für uns als Entwickler aber positiv sein, 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. Rund 3,5 Jahre nach dem Erscheinen von JDK 8 am 18. 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. Aber immerhin hat sich das Warten gelohnt: Neben diversen Verbesserungen im JDK selbst lag bei Java 9 der Hauptfokus auf der Modularisierung, die eine verlässliche Konfiguration und besser strukturierte Programme mit klaren Abhängigkeitsbeziehungen begünstigt.1 Was erwartet Sie im Folgenden? Dieses Buch gibt einen Überblick über diverse wesentliche Erweiterungen in den JDKs 9, 10, 11 und 12. Es werden unter anderem folgende Themen behandelt: API- und Syntaxerweiterungen Wir 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. 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, 1
Allerdings sollte man bedenken, dass sowohl die funktionale Programmierung mit Lambdas als auch die Modularisierung bereits für JDK 7 angekündigt waren.
2
1 Einleitung
wurde um Funktionalität ergänzt. Neben Vereinfachungen beim Prozess-Handling, der Verarbeitung mit Optional 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/2Support. Eher schmerzlich könnte der Wegfall verschiedener Funktionalitäten, etwa rund um JAXB sowie JavaFX, aufgenommen werden. In Java 12 wurden leider die sogenannten »Raw String Literals« als Feature gestrichen, sodass für uns als Entwickler vor allem ein Preview auf Syntaxänderungen bezüglich switch verbleibt. JVM-Änderungen In jeweils eigenen Abschnitten beschäftigen wir uns mit Änderungen in der JVM, die in den neuen Java-Versionen enthalten sind, 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, mit dem Flight Recoder ein neues Tool sowie mit dem Feature »Launch Single-File Source-Code Programs« die Möglichkeit, JavaKlassen ohne explizite vorherige Kompilierung ausführen zu lassen und somit für Scripting einsetzen zu können. Mit Java 12 kann man die Integration des MicrobenchmarkFrameworks JMH (Java Microbenchmarking Harness) als wesentliche Neuerung ansehen. Modularisierung Die 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, aber anderem Bytecode) befinden. Dabei kann aber 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 kann man nun eigenständige Softwarekomponenten (Module) mit einer Sichtbarkeitssteuerung definieren. Das hat allerdings weitreichende Konsequenzen: Sofern man Module verwendet, lassen sich 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.
3
Tipp: Beispiele und der Kompatibilitätsmodus Zum Ausprobieren einiger Neuerungen aus JDK 9, 10, 11 und 12 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 aber mitunter Module nutzen wollen. Das schauen wir uns in eigenen Kapiteln an.
Entdeckungsreise JDK 9, 10, 11 und 12 – 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, 10, 11 und 12. Möge Ihnen der Umstieg auf die neue 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.
214
Teil I Sprach- und API-Erweiterungen in Java 9
215
7
2
Syntaxerweiterungen in JDK 9
Bereits in JDK 7 wurden unter dem Projektnamen Coin verschiedene kleinere Syntaxerweiterungen in Java integriert. Für JDK 9 gab es ein Nachfolgeprojekt, dessen Neuerungen wir uns jetzt anschauen.
2.1
Anonyme 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. Beispiel mit JDK 8 Bis JDK 8 musste man bei der Definition einer anonymen inneren Klasse den Typ noch wie folgt angeben: final Comparator byLengthJdk8 = new Comparator() { ... };
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 byLength = new Comparator() { ... };
8
2 Syntaxerweiterungen in JDK 9
Tipp: Alternative Definitionsvarianten von Komparatoren seit JDK 8 Für Komparatoren bietet es sich an, folgende Neuerungen aus JDK 8 zu nutzen: 1. Einen Lambda-Ausdruck Comparator byLength = (str1, str2) -> Integer.compare(str1.length(), str2.length());
2. Die Methode comparing() aus dem Interface Comparator Comparator byLength = Comparator.comparing(String::length);
Im Anhang A gehe ich auf einige Neuerungen aus JDK 8 ein. Dabei behandle ich unter anderem auch die Erweiterungen bei Komparatoren.
2.2 Erweiterung 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 in Zukunft geplant ist, veraltete Funktionalität aus dem JDK zu entfernen, statt sie – wie bislang für Java üblich – aus Rückwärtskompati-
2.3 Private Methoden in Interfaces
9
bilitätsgründen ewig beizubehalten. Das folgende Beispiel zeigt eine Anwendung, wie sie aus dem JDK stammen könnte: @Deprecated(since = "1.5", forRemoval = true)
Mithilfe der neuen 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.3
Private 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. Unter anderem wurden mit JDK 8 statische Methoden und Defaultmethoden in Interfaces erlaubt. Mit beiden kann man Implementierungen in Interfaces vorgeben.1 Das führt allerdings dazu, dass sich Interfaces kaum mehr von einer abstrakten Klasse unterscheiden: Abstrakte Klassen 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 nun auch die Definition privater Methoden in Interfaces erlaubt ist. 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 1
Dieser Schritt war designtechnisch nicht schön, aber nötig, um Rückwärtskompatibilität und doch Erweiterbarkeit zu erreichen und um vor allem die Neuerungen im Bereich der Streams nahtlos ins JDK 8 integrieren zu können.
10
2 Syntaxerweiterungen in JDK 9
implementieren sollten. Trotz dieser Kritik möchte ich Ihnen das Feature anhand eines Beispiels vorstellen, da es eventuell für Framework-Entwickler von Nutzen sein kann. 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
Dieser Nachteil verliert durch Nutzung einer modernen Microservice-Architektur etwas an Gewicht, da die Designsünde dann relativ isoliert existiert.
2.4 Verbotener Bezeichner ’_’
2.4
11
Verbotener 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.
13
3
Neues und Änderungen in JDK 9
Nachdem wir diverse kleinere Änderungen in der Syntax der Sprache Java kennengelernt haben, wollen wir uns in diesem Kapitel relevante Erweiterungen im JDK anschauen. Erwähnenswert sind das neue Process-API, die Ergänzungen im Stream-API sowie in java.util.Optional, aber auch die neuen Collection-Factory-Methoden. Darüber hinaus findet man Ergänzungen in den Klassen java.io.InputStream und java.util.ResourceBundle sowie diverse Neuerungen, unter anderem auch im Bereich Concurrency: Die Klasse java.util.concurrent.CompletableFuture bietet mehr Funktionalität. Wesentlicher ist vermutlich aber die Unterstützung von Reactive Streams durch die im Package java.util.concurrent neu eingeführte Klasse Flow. Auch im Bereich von Unicode, Grafikformaten sowie Desktop-Technologie mit HiDPI-Support und Unterstützung der Taskbar bzw. des Docks hat sich einiges weiterentwickelt. Außerdem wurden Utility-Klassen wie java.util.Objects und java.util.Arrays sinnvoll ergänzt.
3.1
Neue und erweiterte APIs
Wie bereits angedeutet, wurden in den APIs des JDKs diverse Verbesserungen vorgenommen und Neuerungen integriert, die wir nun thematisch gruppiert anschauen wollen. Dabei beginnen wir mit den Neuerungen im Process-API.
3.1.1 Das neue Process-API Bis einschließlich JDK 8 sind die Möglichkeiten recht eingeschränkt, wenn es darum geht, Prozesse des Betriebssystems zu kontrollieren und zu verwalten. PID mit JDK 8 ermitteln Ein simples Beispiel ist die Ermittlung der ID eines Prozesses, kurz PID genannt. Je nach Plattform muss man dies mit Java 8 unterschiedlich implementieren. Für Linux und Mac OS führt man dazu ein Shell-Kommando mit der Methode exec() aus der Klasse java.lang.Runtime aus – das korrespondierende Windows-Kommando unter Einsatz der Powershell ist als commandsWin definiert:
14
3 Neues und Änderungen in JDK 9
private static long getPidJdk8Style() throws InterruptedException, IOException { // $PPID steht für Parent Process ID, also hier derjenigen der JVM final String[] commands = new String[]{ "/bin/sh", "-c", "echo $PPID" }; // Komplexere Windows-Variante final String[] commandsWin = new String[]{ "powershell", "gwmi win32_process | ?{$_.ProcessID -eq $pid} | " + "select ParentProcessID | fw -c 2"}; // Für Windows: commandsWin nutzen final Process proc = Runtime.getRuntime().exec(commands); if (proc.waitFor() == 0) { try (final InputStream in = proc.getInputStream()) { final int available = in.available(); final byte[] outputBytes = new byte[available]; in.read(outputBytes); final String pid = new String(outputBytes); // rein theoretisch wäre hier eine NumberFormatException abzufangen return Long.parseLong(pid.trim()); } } throw new IllegalStateException("PID is not accessible"); }
Im Listing wird ersichtlich, wie aufwendig das Auslesen der Informationen aus dem InputStream des erzeugten Prozesses ist und dass sogar rein theoretisch der Fall behandelt werden müsste, dass keine gültige Zahl zurückgeliefert wird – was wohl nur in Ausnahmesituationen bei Lesefehlern der Streams geschehen könnte und was wir hier der Vereinfachung halber außer Acht lassen. Hinweis: Die Klasse Runtime Java ist weitestgehend betriebssystemunabhängig. Manchmal ist es aber wünschenswert, externe Programme in Form von Prozessen auszuführen. Dies ist durch den Aufruf von exec() der Klasse Runtime möglich. Dadurch entsteht ein neues java.lang.Process-Objekt. Mit dessen Methode waitFor() kann man blockierend auf das Ende des Prozesses warten und anschließend mit der Methode exitValue() den Rückgabewert erfragen. Zudem erhält man über getOutputStream(), getInputStream() und getErrorStream() Zugriff auf die dem Process zugeordneten Ein- und Ausgabeströme. Dabei sind allerdings einige Details zu beachten. Zunächst ist der Zugriff auf diese Streams insofern wichtig, als dass der erzeugte Prozess keine Konsole zur Ausgabe hat und dadurch die Ausgaben auf System.out an den Vaterprozess weitergeleitet werden. Auch kann man Eingaben an einen Subprozess weiterleiten. Dabei empfiehlt es sich, möglichst zeitnah aus den Streams zu lesen und nicht erst nach Terminierung des Prozesses, da es ansonsten zu Blockierungen und Deadlocks kommen kann.
3.1 Neue und erweiterte APIs
15
PID mit JDK 9 ermitteln Die Abfrage der PID mit Java 9 wird mithilfe der Klasse java.lang.ProcessHandle nun deutlich kürzer, besser lesbar und verständlich: private static long getPidJdk9Style() { return ProcessHandle.current().pid(); }
Neben den genannten Vorteilen bietet die Methode pid() einen betriebssystemunabhängigen Weg zur Ermittlung der Prozess-ID (zumindest aus Sicht des Aufrufers). Beispielprogramm Wir wollen die beiden Methoden in Aktion erleben und deren Ergebnis prüfen. Dazu schreiben wir folgendes Programm: public static void main(final String[] args) throws InterruptedException, IOException { System.out.println("PID old: " + getPidJdk8Style()); System.out.println("PID new: " + getPidJdk9Style()); }
Listing 3.1 Ausführbar als ’P ID E XAMPLE ’
Startet man das Programm P ID E XAMPLE, so sieht man anhand der Ausgabe identischer Prozess-IDs, dass beide Varianten funktional übereinstimmen, beispielsweise wie folgt: PID old: 41948 PID new: 41948
Das Interface ProcessHandle Neben der PID kann man mithilfe von ProcessHandle noch diverse weitere Informationen zu Prozessen auslesen. Dazu gibt es unter anderem folgende Methoden: n n
current() – Ermittelt den aktuellen Prozess als ProcessHandle. info() – Stellt Infos zum Prozess in Form des inneren Interface ProcessHandle.Info bereit, etwa zu Benutzer, Kommando usw. info().command() – Gibt das Kommando als Optional aus einem ProcessHandle.Info zurück. info().user() – Liefert den Benutzer als Optional aus einem ProcessHandle.Info. info().totalCpuDuration() – Ermittelt aus den Infos die benötigte CPU-Zeit als Optional. Die Klasse java.time.Duration entstammt dem
n n n
mit JDK 8 neu eingeführten Date and Time API.1 1
Einen Kurzüberblick für dieses Datums-API bietet Anhang A.
16
3 Neues und Änderungen in JDK 9
Zum besseren Verständnis dieser Methoden betrachten wir ein Beispiel: public static void main(final String[] args) { final ProcessHandle current = ProcessHandle.current(); printInfo(current); } private static void printInfo(final ProcessHandle current) { System.out.println("PID: " + current.pid()); System.out.println("Info: " + current.info()); System.out.println("Command: " + current.info().command()); System.out.println("CPU-Usage: " + current.info().totalCpuDuration()); }
Listing 3.2 Ausführbar als ’P ROCESS H ANDLE E XAMPLE ’
Das Programm P ROCESS H ANDLE E XAMPLE gibt in etwa Folgendes aus (gekürzt): PID: Info:
13670 [user: Optional[michaeli], cmd: /Library/Java/JavaVirtualMachines/jdk -9.jdk/Contents/Home/bin/java, args: [-Dfile.encoding=UTF-8, -Duser.country =DE, -Duser.language=de, -Duser.variant, -cp, /Users/michaeli/Desktop/ PureJava9/quelltext/build/libs/Java9-all.jar:/Users/michaeli/Desktop/ PureJava9/quelltext/build/requiredLibs, ch3_1.processapi. ProcessHandleExample], startTime: Optional[2017-04-06T17:21:56.927Z], totalTime: Optional[PT0.230888S]] Command: Optional[/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/ bin/java] CPU-Usage: Optional[PT0.308141S]
Neben der PID werden diverse Informationen aus dem Info-Objekt aufgelistet, exemplarisch separat nochmals die Werte für command() und totalCpuDuration(). Für das mit command() als Optional ermittelte Kommando erkennen wir, dass es sich um das Programm java aus JDK 9 handelt, das laut totalCpuDuration() etwa 0.31 Sekunden CPU-Zeit verbraucht hat, wie man anhand von Optional sieht. Alle Prozesse mit ProcessHandle abfragen Neben Informationen zum aktuellen Prozess lassen sich Informationen für alle Prozesse des Benutzers sowie alle Subprozesse zu einem Prozess wie folgt ermitteln: n n
allProcesses() – Liefert alle Prozesse als Stream. children() – Ermittelt zu einem Prozess alle seine (direkten) Subprozesse als Stream.
Im nachfolgenden Beispiel iterieren wir über das Ergebnis von allProcesses() und geben Infos zu solchen Prozessen aus, die Subprozesse besitzen. Die Anzahl an Subprozessen können wir durch Aufruf von children().count() erfragen:
3.1 Neue und erweiterte APIs
17
public static void main(final String[] args) { System.out.println("All Processes:"); showInfoForAllProcesses(); } private static void showInfoForAllProcesses() { ProcessHandle.allProcesses().forEach(processHandle -> { final Stream children = processHandle.children(); final long count = children.count(); if (count > 0) { System.out.println("Info: " + processHandle.info() + " has " + count + " children"); } }); }
Listing 3.3 Ausführbar als ’A LL P ROCESS H ANDLES E XAMPLE ’
Das Programm A LL P ROCESS H ANDLES E XAMPLE produziert die folgenden Ausgaben (gekürzt), die eine Liste der zurückgelieferten Informationen widerspiegeln: All Processes: Info: [user: Optional[michaeli], cmd: /Applications/Adobe Acrobat Reader DC.app/ Contents/MacOS/AdobeReader, args: [-psn_0_3822501], startTime: Optional [2016-08-02T21:16:30.322Z]] has 3 children ... Info: [user: Optional[michaeli], cmd: /System/Library/CoreServices/Dock.app/ Contents/MacOS/Dock, startTime: Optional[2016-07-24T08:17:12.938Z]] has 1 children Info: [user: Optional[root], startTime: Optional[2016-07-24T08:16:40.564Z]] has 285 children ...
Prozesse mit ProcessHandle kontrollieren Neben der Bereitstellung und Abfrage von Informationen zu Prozessen existieren auch verschiedene Möglichkeiten, Prozesse zu beenden sowie auf das Ende eines Prozesses zu reagieren. Dazu findet man im Interface ProcessHandle folgende Methoden: n n
of(long) – Liefert ein Optional zu einer gegebenen PID. destroy() – Terminiert einen Prozess, sofern dies erlaubt ist. Ansonsten, etwa für den mit current() ermittelten Prozess, wird eine Exception ausgelöst: Exception in thread "main" java.lang.IllegalStateException: destroy of current process not allowed
n
onExit() – Liefert ein CompletableFuture zurück, das
man dazu nutzen kann, verschiedene Aktionen als Reaktion auf das Ende eines Prozesses auszuführen.
18
3 Neues und Änderungen in JDK 9
Zur Demonstration dieser Methoden wollen wir ein Beispiel erstellen. Es soll zunächst mit Runtime.exec() ein Prozess gestartet werden. Als Rückgabe erhält man ein Process-Objekt. Dieses bietet seit JDK 9 ebenfalls die Methode pid() sowie diverse andere, die auch durch ProcessHandle bereitgestellt werden. Auch kann man ein Process-Objekt durch einen Aufruf von toHandle() in ein ProcessHandle-Objekt transformieren: 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); System.out.println("Started process is " + sleeper.pid()); // Process => ProcessHandle final ProcessHandle sleeperHandle = ProcessHandle.of(sleeper.pid()). orElseThrow(IllegalStateException::new); final ProcessHandle sleeperHandle2 = sleeper.toHandle(); System.out.println("Same handle? " + sleeperHandle.equals(sleeperHandle2)); // Exit Handler registrieren final Runnable exitHandler = () -> System.out.println("exitHandler called"); sleeperHandle.onExit().thenRun(exitHandler); System.out.println("Registered exitHandler"); // Den Prozess zerstören und ein wenig warten, // damit onExit() ausgeführt werden kann System.out.println("Destroying process " + sleeperHandle.pid()); sleeperHandle.destroy(); Thread.sleep(500); }
Listing 3.4 Ausführbar als ’C ONTROL P ROCESS E XAMPLE ’
Startet man das Programm C ONTROL P ROCESS E XAMPLE, so können wir anhand der folgenden Ausgaben recht gut die im Listing definierten Aktionen nachvollziehen: Started process is 60392 Same handle? true Registered exitHandler Destroying process 60392 exitHandler called
Fazit Wir haben in verschiedenen Beispielen kennengelernt, wie man mit dem neuen ProcessAPI mit Prozessen des Betriebssystems interagieren oder zumindest Informationen darüber gewinnen kann. Die große Stärke des Process-APIs ist, dass die Aktion aus Sicht eines Java-Entwicklers betriebssystemunabhängig erfolgen kann. Damit kommt man Javas Versprechen von einer weitestgehend betriebssystemunabhängigen Programmierung wieder ein Stück näher.
3.1 Neue und erweiterte APIs
19
3.1.2 Collection-Factory-Methoden Das Erzeugen von Collections für ein paar vordefinierter Werte ist in Java mitunter etwas umständlich. Sprachen wie Groovy oder Python bieten dafür eine spezielle Syntax, sogenannte Collection-Literale. Bereits 2009 hat man auch für Java über eine Integration einer einfacheren Schreibweise zur Erzeugung von und zum Zugriff auf Collections nachgedacht. Leider wurde nichts Derartiges realisiert, obwohl es einige, vielversprechende Vorschläge gab. Vorschlag: Collection-Literale und Collection-Erzeugung Nachfolgendes Listing zeigt, wie eine mögliche Syntax für Collection-Literale für die im Package java.util definierten Collections List, Set und Map aussehen könnte. Dabei werden die Elemente der Collection in geschweifte oder eckige Klammern eingeschlossen: // Leider weder mit JDK 8 noch JDK 9 umgesetzt final List newStyleList = ["item1", "item2"]; final Set newStyleSet = {"Tim", "Mike" }; final Map newStyleMap = ["key1" : "value1", "key2" : "value2"];
Vorschlag: Collection-Literale und Datenzugriff Während man auf Arrays indiziert mit eckigen Klammern zugreift, muss man für Listen die Methode get(int) und für Maps die Methode get(K) nutzen. Wertzuweisungen erfolgen durch Aufruf von set(int,E) bzw. put(K,V). Für indizierte Zugriffe und Wertzuweisungen auf Listen bzw. Zugriffe auf Schlüssel und Werte bei Maps war für Java eine Notation analog zu indizierten Array-Zugriffen vorgesehen, wodurch explizite Methodenaufrufe überflüssig geworden wären: // Collection-Literale leider weder mit JDK 8 noch JDK 9 umgesetzt newStyleList[0] = "newValue"; final String valueAtPos0 = newStyleList[0]; // => newValue
Realisierung mit JDK 9 In Java 9 gibt es leider keine Collection-Literale in der zuvor beschriebenen Form. Stattdessen wurde eine Armada an Factory-Methoden mit den Namen of() die Interfaces List, Set und Map integriert, die sich wie folgt zur Erzeugung von Collections nutzen lassen: final List names = List.of("Mike", "Tim", "Tom"); final Set primeNumbers = Set.of(2, 3, 5, 7, 11); final Map numberMapping = Map.of(5, "five", 6, "six");
20
3 Neues und Änderungen in JDK 9
Schauen wir uns dazu ein Beispiel für jede der Methoden an, dass darüber hinaus die zusätzliche Methode ofEntries() im Interface Map und deren Nutzung verdeutlicht:2 public static void main(final String[] args) { // Collection Factory Methods final List names = List.of("MAX", "MORITZ", "MIKE"); names.forEach(name -> System.out.println(name)); // oder System.out::println final Set numbers = Set.of(1, 2, 3); numbers.forEach(number -> System.out.println(number)); final Map mapping = Map.of(5, "five", 6, "six"); final Map mapping2 = Map.ofEntries(entry(5, "five"), entry(6, "six")); mapping.forEach((key, value) -> System.out.println(key + ":" + value)); mapping2.forEach((key, value) -> System.out.println(key + ":" + value)); }
Listing 3.5 Ausführbar als ’C OLLECTION FACTORY M ETHODS E XAMPLE ’
Das Programm C OLLECTION FACTORY M ETHODS E XAMPLE gibt Folgendes aus: MAX MORITZ MIKE 1 2 3 6:six 5:five 6:six 5:five
Die Reihenfolge der Elemente bei Sets und Maps ist bei der Nutzung der CollectionFactory-Methoden allerdings nicht definiert und insbesondere wird die Einfügereihenfolge oftmals nicht beibehalten. Auch die Reihenfolge bei einer Iteration ist zufällig, sodass es durchaus etwa zu folgenden Ausgaben kommen kann: MAX MORITZ MIKE 3 2 1 5:five 6:six 5:five 6:six
2
Zur besseren Lesbarkeit erfolgt dazu ein statischer Import von java.util.Map.entry. Zudem sieht man, dass ofEntries() eine klarere Gruppierung von Schlüssel und Wert erlaubt, was vor allem dann hilfreich ist, wenn diese vom selben Typ sind.
3.1 Neue und erweiterte APIs
21
Besonderheiten bei der Konstruktion In den jeweiligen Collection-Interfaces sind die Collection-Factory-Methoden explizit für 0 bis 10 Parameter sowie als VarargVariante definiert. Allerdings führt diese performanceoptimierte Realisierung zu einer Menge an Methoden. Zudem gibt es für den Fall mit dem Vararg-Parameter noch einen speziellen switch-case, der die spezialisierten Methoden aufruft: static List of() { return ImmutableCollections.List0.instance(); } static List of(E e1) { return new ImmutableCollections.List1(e1); } ... @SafeVarargs @SuppressWarnings("varargs") static List of(E... elements) { switch (elements.length) { case 0: return new ImmutableCollections.List0(); case 1: return new ImmutableCollections.List1(elements[0]); case 2: return new ImmutableCollections.List2(elements[0], elements[1]); default: return new ImmutableCollections.ListN(elements); } }
Glücklicherweise sieht man diese Implementierungsdetails nur dann, wenn man in den JDK-Sourcecode schaut. Allerdings scheint diese Unschönheit zumindest in der IDE bei der Auto-Completion durch, die eine Horde gleichnamiger Methoden anbietet. Space Efficiency Das obige Listing zeigt, dass spezielle innere Klassen aus der Klasse java.util.ImmutableCollections erzeugt werden. Neben ihrer Unveränderlichkeit und auf Arrays basierenden Spezialimplementierungen zeichnen sich die durch die Collection-Factory-Methoden erzeugten Collections dadurch aus, dass sie viel weniger Speicherplatz verbrauchen als diejenigen aus dem Collections-Framework. Betrachten wir ein java.util.HashSet mit zwei Elementen bzw. ein mit den neuen Collection-Factory-Methoden erzeugtes Set. Die folgende Variante für JDK 8 benötigt über 150 Bytes, wogegen diejenige für JDK 9 lediglich um 20 Bytes verbraucht:3 final Set namesSet = new HashSet(); names.add("MAX"); names.add("TIM"); final Set unmodifiableNamesJDK8 = Collections.unmodifiableSet(namesSet); final Set unmodifiableNamesJDK9 = Set.of("MAX", "TIM"); 3
Die Zahlen basieren auf Stuart Marks’ Vortrag »New Collections APIs for Java 9«, der online unter https://www.youtube.com/watch?v=OJrIMv4dAek verfügbar ist.
22
3 Neues und Änderungen in JDK 9
Besonderheiten bei Duplikaten Beim Einsatz der Collection-Factory-Methoden sollte man ein Detail für Set und Map kennen: Bekanntermaßen modelliert ein Set das mathematische Konzept einer Menge und enthält somit keine Duplikate. Das gilt auch für die Schlüssel in Maps. Diese Eigenschaft wurde bei den bisherigen Collections automatisch sichergestellt, indem beim Einfügen von Elementen gegebenenfalls Duplikate aussortiert wurden.4 Das war für diverse Anwendungsfälle ein recht praktisches Feature. Die Collection-Factory-Methoden weisen allerdings eine nicht überraschungsfreie Besonderheit auf: Für Sets wird zum Konstruktionszeitpunkt die Duplikatfreiheit geprüft. Ist diese nicht gegeben, so wird eine Exception ausgelöst. Gleiches gilt auch für Duplikate bei den Schlüsseln von Maps. Bei Listen findet dagegen – wie erwartet – keine Duplikatsprüfung statt. Schauen wir uns ein Beispiel an: final Set names = Set.of("MAX", "Moritz", "MAX");
Bei dieser Variablendefinition kommt es zur Laufzeit zu folgender Exception: java.lang.IllegalArgumentException: duplicate element: MAX at java.base/java.util.ImmutableCollections$SetN.(ImmutableCollections. java:462)
Weil die Collections direkt anhand der übergebenen Werte konstruiert werden, kann man jedoch auch einen Grund für dieses Verhalten finden, nämlich die Vermeidung von Inkonsistenzen durch Flüchtigkeitsfehler in Form einer Mehrfachangabe von Werten. Hinweis: Spezifische Typen erzeugee Die durch die inneren Klassen von ImmutableCollections erzeugten Collections erfüllen die jeweiligen Interfaces aus dem Collections-Framework. Wenn man jedoch eine Sortierung wie bei java.util.TreeSet und java.util.TreeMap wünscht, wird dies nicht direkt unterstützt.
Fazit Die Collection-Factory-Methoden können mich nicht vollständig überzeugen. Für die Definition kleiner Wertbestände gefallen Sie mir eigentlich schon recht gut, auch wenn sie nicht ganz so elegant in der Schreibweise sind, wie es Collection-Literale wären. Positiv können die Collection-Factory-Methoden ihre enorme Speichereffizienz ins Feld führen. Oftmals ist zudem die Unveränderlichkeit von Vorteil, etwa wenn man diese Collections an andere Programmteile weitergibt. 4
Das erfordert, dass die steuernden Methoden wie equals(), hashCode() usw. korrekt implementiert sind. Details finden Sie in meinem Buch »Der Weg zum Java-Profi« [4].
3.1 Neue und erweiterte APIs
23
Durchaus praktisch wäre es gewesen, auch in den spezifischen Collections, wie ArrayList oder TreeSet, jeweils of()-Methoden anzubieten, um bei Bedarf
einen speziell benötigten Typ erzeugen zu können. Als potenziellen Negativpunkt sehe ich, dass die Duplikatbehandlung für Set zumindest nicht überraschungsfrei ist – die ausgelöste Exception ist meiner Meinung nach sogar kontraintuitiv. Auch sollte man nicht zu sehr hinter die Kulissen der Collection-Factory-Methoden schauen, denn deren Realisierung als eine Horde überladener, statischer Methoden in Interfaces ist eher fragwürdig. Insgesamt lässt sich festhalten, dass die zuvor genannten Negativpunkte für den Einsatz in der Praxis eine untergeordnete Rolle spielen. Als Nutzer fällt vor allem die Vereinfachung bei der Konstruktion von Collections positiv ins Gewicht.
3.1.3 Reactive Streams und die Klasse Flow Die Unterstützung von Reactive Streams5 ist die größte Neuerung in JDK 9 im Bereich der Concurrency. Dabei spielen die Klasse java.util.concurrent.Flow und deren innere Interfaces eine wesentliche Rolle. Schnelleinstieg Reactive Streams Themen wie Performance, Skalierbarkeit und Ausfallsicherheit gewinnen heutzutage zunehmend an Bedeutung. Das adressieren Frameworks wie Akka, RxJava, Vert.x und Reactor, indem sie eine asynchrone, eventgetriebene, nicht blockierende Verarbeitung unterstützen – man spricht von Reactive Programming.6 Das Essenzielle ist dabei, dass voneinander unabhängig ausgeführte Verarbeitungseinheiten über Events bzw. Messages miteinander kommunizieren. Dazu registrieren sich Subscriber (Empfänger) bei einem Publisher (Sender). Bei der Kommunikation kann es zu Engpässen kommen, wenn ein Publisher zu langsam oder zu schnell arbeitet. Im ersten Fall werden Subscriber eventuell nur teilweise ausgelastet, im zweiten Fall überlastet. Früher hat man als Abhilfe einen Eingangspuffer aufseiten des Empfängers genutzt. Um Speicherprobleme zu vermeiden, sollte der Eingangspuffer größenbeschränkt sein. Alternativ kann der Sender auch nur eine gewisse Maximalmenge an Daten verschicken. Dadurch verschwendet man aber potenziell Performance, wenn der Empfänger eine schnellere Abarbeitung bewerkstelligen könnte. Reactive Streams adressieren die beschriebenen Engpässe im Datenfluss. Dazu verfolgen Reactive Streams das Konzept der Backpressure (Gegendruck oder Rückstau). Die Idee ist recht simpel: Der Empfänger kann dem Sender mitteilen, wie viele Daten er verarbeiten kann. Der Subscriber fordert aktiv Daten an und der Publisher schickt maximal so viele Daten wie angefordert. Dadurch ermöglichen Reactive Streams eine Selbstregulierung, bei der die Datenverarbeitung an einen möglicherweise temporär 5 6
http://www.reactive-streams.org/ Details dazu finden Sie unter http://www.reactivemanifesto.org/.
24
3 Neues und Änderungen in JDK 9
langsamen Verarbeiter angepasst wird. Schauen wir uns im Anschluss die mit JDK 9 eingeführte Umsetzung an. Anforderung
Publisher
Subscriber Daten
Abbildung 3-1 Schematische Zusammenarbeit zwischen Publisher und Subscriber
Die Klassen Flow und SubmissionPublisher im Überblick Java 9 unterstützt Reactive Streams mit der Klasse Flow. Das geschieht relativ leichtgewichtig und basiert auf den vier Interfaces Publisher, Subscriber, Subscription und Processor, die innerhalb von Flow definiert sind. Publisher und Subscriber implementiert man gewöhnlich selbst und eine Subscription hilft beim Austausch von Daten. Schließlich kombiniert ein Processor sowohl Publisher als auch Subscriber in einem Interface. Das Interface Publisher Ein Publisher ist ein Veröffentlicher (mitunter auch Erzeuger) von Dingen, die von einem oder mehreren SubscriberObjekten verarbeitet werden. Dazu können sich diese bei dem Publisher registrieren. Das zugehörige Interface ist minimalistisch wie folgt definiert: @FunctionalInterface public static interface Publisher { public void subscribe(Subscriber