image

Image

Dipl.-Inform. Michael Inden ist Oracle-zertifizierter Java-Entwickler für JDK 6. Nach seinem Studium in Oldenburg war er lange Zeit als Softwareentwickler und -architekt bei verschiedenen internationalen Firmen tätig.

Dabei hat er fast 15 Jahre Erfahrung beim Entwurf objektorientierter Softwaresysteme gesammelt, an diversen Fortbildungen und an mehreren Java-One-Konferenzen in San Francisco teilgenommen. Sein besonderes Interesse gilt dem Design qualitativ hochwertiger Applikationen mit ergonomischen, grafischen Oberflächen sowie dem Coaching von Kollegen.

Image

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.de/plus

Java 8 – Die Neuerungen

Lambdas, Streams, Date and Time API und JavaFX 8 im Überblick

2., aktualisierte und erweiterte Auflage

Michael Inden

Image

Michael Inden
michael_inden@hotmail.com

Lektorat: Dr. Michael Barabas

Technischer Review: Torsten Horn, Aachen

Copy-Editing: Ursula Zimpfer, Herrenberg

Satz: Michael Inden

Herstellung: Susanne Bröckelmann

Umschlaggestaltung: Helmut Kraus, www.exclam.de

Druck und Bindung: M.P. Media-Print Informationstechnologie 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

Buch: 978-3-86490-290-1

PDF: 978-3-86491-749-3

epub: 978-3-86491-750-9

mobi: 978-3-86491-751-6

2., aktualisierte und erweiterte Auflage 2015

Copyright © 2015 dpunkt.verlag GmbH

Wieblinger Weg 17

69123 Heidelberg

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.

5 4 3 2 1 0

Inhaltsverzeichnis

1      Einleitung

2      Lambda-Ausdrücke

2.1    Einstieg in Lambdas

2.1.1    Lambdas am Beispiel

2.1.2    Functional Interfaces und SAM-Typen

2.1.3    Type Inference und Kurzformen der Syntax

2.1.4    Lambdas als Parameter und als Rückgabewerte

2.1.5    Unterschiede: Lambdas vs. anonyme innere Klassen

2.2    Defaultmethoden

2.2.1    Interface-Erweiterungen

2.2.2    Vorgabe von Standardverhalten

2.2.3    Erweiterte Möglichkeiten durch Defaultmethoden

2.2.4    Spezialfall: Was passiert bei Konflikten?

2.2.5    Vorteile und Gefahren von Defaultmethoden

2.3    Statische Methoden in Interfaces

2.4    Methodenreferenzen

2.5    Exceptions in Lambdas

2.6    Fazit

2.7    Übungen zu Lambdas, Methodenreferenzen und Defaultmethoden

3      Bulk Operations on Collections

3.1    Externe vs. interne Iteration

3.1.1    Externe Iteration

3.1.2    Interne Iteration

3.1.3    Externe vs. interne Iteration an einem Beispiel

3.2    Collections-Erweiterungen

3.2.1    Das Interface Predicate<T>

3.2.2    Die Methode Collection.removeIf()

3.2.3    Das Interface UnaryOperator<T>

3.2.4    Die Methode List.replaceAll()

3.3    Streams

3.3.1    Streams erzeugen — Create Operations

3.3.2    Intermediate und Terminal Operations im Überblick

3.3.3    Zustandslose Intermediate Operations

3.3.4    Zustandsbehaftete Intermediate Operations

3.3.5    Terminal Operations

3.3.6    Wissenswertes zur Parallelverarbeitung

3.4    Filter-Map-Reduce

3.4.1    Herkömmliche Realisierung

3.4.2    Filter-Map-Reduce mit JDK 8

3.5    Datenaufbereitung mit Kollektoren

3.5.1    Das Interface Collector und die Methode collect()

3.5.2    Vertiefung bereits vorgestellter Kollektoren

3.5.3    Aufbereitung statistischer Daten

3.5.4    Aufbereitung als Map – einfache Gruppierungen

3.5.5    Aufbereitung mit Gruppierung und Partitionierung

3.6    Fallstricke bei Lambdas und funktionaler Programmierung

3.6.1    Java-Fehlermeldungen werden zu komplex

3.6.2    Fallstrick: Imperative Lösung 1:1 funktional umsetzen

3.6.3    Fallstrick: Zustandsbehaftete Lambdas

3.7    Fazit

3.8    Übungen zu Collections und Bulk Operations

3.9    Übungen zu Streams und Filter-Map-Reduce

4      JSR-310: Date and Time API

4.1    Datumsverarbeitung vor JSR-310

4.2    Überblick über die neu eingeführten Typen

4.2.1    Neue Aufzählungen, Klassen und Interfaces

4.2.2    Die Aufzählungen DayOfWeek und Month

4.2.3    Die Klassen MonthDay, YearMonth und Year

4.2.4    Die Klasse Instant

4.2.5    Die Klasse Duration

4.2.6    Die Aufzählung ChronoUnit

4.2.7    Die Klassen LocalDate, LocalTime und LocalDateTime

4.2.8    Die Klasse Period

4.2.9    Die Klasse ZonedDateTime

4.2.10   Zeitzonen und die Klassen ZoneId und ZoneOffset

4.2.11   Die Klasse Clock

4.2.12   Formatierung und Parsing – die Klasse DateTimeFormatter

4.3    Datumsarithmetik

4.4    Das neue Date and Time API im Einsatz

4.4.1    Beispiel: Berechnung einer Zeitdifferenz

4.4.2    Simulationen und die Klasse SpecialClock

4.4.3    Selbst definierte TemporalAdjusters

4.4.4    Interoperabilität mit Legacy-Code

4.5    Fazit

4.6    Übungen zum Date and Time API

5      Einstieg JavaFX 8

5.1    Einführung – JavaFX im Überblick

5.1.1    Motivation für JavaFX und Historisches

5.1.2    Grundsätzliche Konzepte

5.1.3    Layoutmanagement

5.2    Deklarativer Aufbau des GUIs

5.2.1    Deklarative Beschreibung von GUIs

5.2.2    Hello-World-Beispiel mit FXML

5.2.3    Diskussion: Design und Funktionalität strikt trennen

5.3    Rich-Client Experience

5.3.1    Gestaltung mit CSS

5.3.2    Effekte

5.3.3    Animationen

5.4    Properties, Data Binding und Observable Collections

5.4.1    Properties

5.4.2    Bindings

5.4.3    Observable Collections

5.4.4    Dynamisches Filtern von ObservableList

5.5    Neuerungen in JavaFX 8

5.5.1    Unterstützung von Lambdas als EventHandler

5.5.2    Neue Controls

5.5.3    Filter- und sortierbare Listen

5.5.4    Texteffekte

5.5.5    JavaFX 3D

5.6    Neuerungen in JavaFX 8 Update 40

5.6.1    Dialoge

5.6.2    Neuerungen in Bedienelementen

5.7    Fazit

5.8    Übungen zu JavaFX 8

6      Weitere Änderungen in JDK 8

6.1    Erweiterungen im Interface Comparator<T>

6.2    Die Klasse Optional<T>

6.2.1    Grundlagen zur Klasse Optional<T>

6.2.2    Weiterführendes Beispiel und Diskussion

6.3    Parallele Operationen auf Arrays

6.4    Erweiterungen im Interface Map<K,V>

6.5    Erweiterungen im Bereich Concurrency

6.6    »Nashorn« – die neue JavaScript-Engine

6.7    Keine Permanent Generation mehr

6.8    Base64-Codierungen

6.9    Erweiterungen im Bereich Reflection

6.10  Erweiterungen im NIO und der Klasse Files

6.11  Änderungen bei Annotations

6.12  Berechnungen mit Überlaufprüfung

6.13  Übungen zu Diverses

7      Java 8 im Praxiseinsatz

7.1    Erste Schritte zur Informationsaufbereitung

7.2    Grafische Darstellung

7.3    Fazit

8      Tipps zur Migration von Java 7 auf Java 8

8.1    Stolpersteine in den Bibliotheken

8.1.1    Verarbeitung mit HashMaps

8.1.2    Unterschiede beim Einsatz von SimpleDateFormat

8.1.3    Rundung beim NumberFormat

8.1.4    Änderungen in der JavaScript-Engine

8.2    Externe in interne Iterationen überführen

8.3    Von Swing zu JavaFX

8.3.1    JavaFX in Swing einbinden

8.3.2    Swing in JavaFX einbinden

9      Zusammenfassung und Ausblick

9.1    Zusammenfassung und Fazit

9.2    Ausblick auf JDK 9: Mit JDK 8 nicht umgesetzte Features

9.2.1    Integration von Collection-Zugriffen

9.2.2    Vergleiche von Enums mit Operatoren

9.3    Weiterführende Literatur

A     Java und funktionale Programmierung

A.1  Programmierparadigmen im Überblick

A.2  Funktionale Programmierung an Beispielen

Literaturverzeichnis

Vorwort zur zweiten Auflage

Als Erstes möchte ich mich bei allen Lesern für die positive Resonanz zur ersten Auflage bedanken. So kommt es nun zu dieser zweiten Auflage, die einige Überarbeitungen sowie Erweiterungen enthält. Das bot sich an, weil Oracle zwischenzeitlich verschiedene Ergänzungen vor allem im Bereich JavaFX ins JDK aufgenommen hat. Außerdem sind in diese zweite Auflage die Rückmeldungen und Erkenntnisse aus einigen von mir gehaltenen internen und externen Schulungen sowie meine Erfahrungen aus einem Real-World-Projekt zur Migration von Java 7 auf Java 8 eingeflossen. Schließlich habe ich im gesamten Text ein paar Tippfehler und kleinere Unstimmigkeiten korrigiert.

Im Speziellen unterscheidet sich diese Auflage von der Erstauflage durch folgende Erweiterungen und Umgestaltungen:

Danksagung

Bei der Erstellung des Manuskripts konnte ich auf ein starkes Team an Korrekturlesern zurückgreifen. Es ist mir eine große Freude, von den unterschiedlichen Sichtweisen und Erfahrungen verschiedener Leute profitieren zu dürfen.

Den einen oder anderen Tipp erhielt ich von Dirk Lemmermann und Merten Driemeyer. Zudem hat Prof. Dr. Carsten Kern mit verschiedenen hilfreichen Anmerkungen zur Verbesserung beigetragen. Ein besonderer Dank geht an Andreas Schöneck für die schnellen Rückmeldungen auch zu später Stunde.

Auch einige Kollegen meines Arbeitgebers Zühlke Engineering AG haben mich direkt oder indirekt unterstützt. Zunächst einmal danke ich der Zühlke Academy. Meine dort gehaltenen Java-8-Kurse bildeten die Basis für die Übungsaufgaben in diesem Buch. Tatkräftig haben die Zühlkianer Joachim Prinzbach, Marius Reusch und Christoph Schmitz durch ihre Kommentare zur Klarheit beigetragen. Vielen Dank dafür!

Ebenso geht ein Dankeschön an das Team des dpunkt.verlags (Dr. Michael Barabas, Martin Wohlrab, Vanessa Wittmer 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. Mittlerweile kennt sie die teilweise aufkommende Hektik zum Abschluss eines Buchprojekts und freut sich auf die Zeit danach.

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 auffällt, 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:

michael_inden@hotmail.com

Zürich und Aachen, im Juni 2015
Michael Inden

Vorwort

Zunächst einmal bedanke ich mich bei Ihnen, dass Sie sich für dieses Buch entschieden haben, um sich über die Neuerungen von Java 8 zu informieren. In den nachfolgenden Kapiteln möchte ich Ihnen diese brandaktuelle Java-Version mit ihren umfangreichen Erweiterungen näherbringen. Insbesondere sind Lambda-Ausdrücke und das Stream-API wegweisende Neuerungen, durch die nun neben der objektorientierten auch die funktionale Programmierung in Java möglich wird. Auch die lange Zeit stiefmütterlich behandelte Datumsarithmetik wurde in Java 8 aufpoliert. Doch damit nicht genug: Die GUI-Technologie JavaFX wurde sowohl um neue Bedienelemente als auch um die Unterstützung für Darstellungen in 3D erweitert. JavaFX schickt sich an, Swing bald als GUI-Framework abzulösen.

Wer sollte dieses Buch lesen?

Dieses Buch ist kein Buch für Programmierneulinge, sondern richtet sich an all diejenigen Leser, die einen fundierten Überblick über die mit Java 8 eingeführten Neuerungen erhalten wollen. Ich gehe also davon aus, dass Sie bereits einiges an Erfahrung mit Java mitbringen. Damit der Umstieg und das Nachvollziehen der Beispiele zu Java 8 leichter fällt, wird oftmals ein Vergleich zu einer herkömmlichen Lösung mit Java 7 dargestellt.

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 einfacher macht. Dies ist etwa für JavaFX der Fall: Dort beginne ich mit einer Darstellung der Grundlagen, weil die deutschsprachige Literatur gerade auf diesem Gebiet recht spärlich ist und sich sonst die Neuerungen aus JavaFX 8 nicht so gut nachvollziehen lassen würden.

Dieses Buch richtet sich im Speziellen an zwei Zielgruppen: Zum einen sind dies engagierte Hobbyprogrammierer, Informatikstudenten und Berufseinsteiger, die Java als Sprache beherrschen und nun neugierig auf die weitreichenden Änderungen in Java 8 sind. Zum anderen ist das Buch für erfahrenere Softwareentwickler und -architekten gedacht, die ihr Wissen ergänzen wollen, um für zukünftige Projekte abschätzen zu können, wann und in welchen Bereichen Java 8 eine gewinnbringende Alternative darstellen kann.

Was vermittelt dieses Buch?

Sie als Leser erhalten neben Theoriewissen eine Vertiefung durch praktische Beispiele, sodass der Umstieg auf Java 8 in eigenen Projekten erfolgreich gemeistert werden kann. Der Fokus dieses Buchs liegt auf dem praktischen Nutzen und den zugrunde liegenden Konzepten. Zur Verdeutlichung werden vereinfachte Beispiele aus dem realen Programmiereralltag genutzt. Um den Rahmen des Buchs nicht zu sprengen, stellen die abgebildeten Programmlistings häufig nur Ausschnitte aus lauffähigen Programmen dar. Deren Name wird in Kapitälchenschrift, etwa DATEPICKEREXAMPLE, angegeben.

Sourcecode und ausführbare Programme

Der Sourcecode kann auf der Webseite www.dpunkt.de/java-8 heruntergeladen werden. Zudem befindet sich dort ein Eclipse-Projekt, über das sich alle Programme ausführen lassen. Idealerweise nutzen Sie dazu ein aktuelles Eclipse 4.4 oder neuer. Zumindest benötigen Sie aber Eclipse 4.3.2 mit einem Update zur JDK-8-Unterstützung (frei verfügbar unter http://download.eclipse.org/eclipse/updates/4.3-P-builds/).

Aufbau dieses Buchs

Kapitel 1 Einführend erhalten Sie einen kurzen Überblick zu Java 8 und seine wegweisenden Neuerungen.

Kapitel 2 Kapitel 2 startet dann mit Lambdas, einer der bedeutsamsten Änderungen der Sprache seit der Einführung von Generics in Java 5. Lambdas führen zu einer vollkommen neuen Denkweise und bilden die Grundlage für die funktionale Programmierung mit Java.

Kapitel 3 Kapitel 3 zeigt dann, wie sich Lambdas gewinnbringend mit den diversen Erweiterungen im Collections-Framework, insbesondere dem Stream-API, kombinieren lassen. Dort wird unter anderem eine mächtige Filter-Map-Reduce-Funktionalität bereitgestellt – ähnlich wie man dies von NoSQL-Datenbanken zur Verarbeitung großer Datenmengen (Stichwort: Big Data) kennt.

Kapitel 4 Lange Zeit war die Verarbeitung von Datums- und Zeitangaben mit Java-Bordmitteln eher mühsam und zudem fehlerträchtig. Mit Java 8 ändert sich dies grundlegend. Das neue Date and Time API bereitet in seiner Nutzung viel Freude. Kapitel 4 gibt dazu einen Überblick.

Kapitel 5 Nicht nur intern, sondern auch im Bereich der Benutzeroberflächen wurde in Java 8 einiges verbessert. Zudem ist JavaFX nun fester Bestandteil des JDKs und wurde an die Versionsnummer von Java angepasst. Neben Detailverbesserungen sind die zwei Bedienelemente DatePicker und TreeTableView sowie die Unterstützung von 3D-Darstellungen bedeutende Erweiterungen. Kapitel 5 beginnt mit einem allgemeinen Einstieg in JavaFX und geht danach auf die Besonderheiten von JavaFX 8 ein.

Kapitel 6 Neben den in den vorangegangenen Kapiteln behandelten recht fundamentalen Änderungen enthält Java 8 noch eine Vielzahl weitere, zum Teil kleinere Verbesserungen, die aber allesamt das Programmiererleben deutlich erleichtern. Einige wesentliche werden in Kapitel 6 vorgestellt.

Kapitel 7 In Kapitel 7 stelle ich ein mit Java 8 umgesetztes reales Projekt vor und zeige, was sich mit Java 8 vereinfacht, und motiviere, warum Sie darauf wechseln sollten.

Kapitel 8 Bestimmt werden einige von Ihnen schon jetzt oder aber recht bald vor der Aufgabe und Herausforderung stehen, vorhandene Projekte auf Java 8 zu migrieren. Dieses Kapitel beschreibt einige mögliche Fallstricke sowie Lösungen, die den Umstieg erleichtern.

Kapitel 9 Kapitel 9 rekapituliert noch einmal die Neuerungen in Java 8 und zieht ein Fazit. Zudem wagen wir dort einen Ausblick auf mögliche Funktionalitäten in JDK 9. Abgerundet wird das Kapitel durch eine Übersicht zu weiterer Literatur zu Java 8, Lambdas und funktionaler Programmierung.

Anhang A Anhang A liefert eine knappe Einführung in verschiedene Programmierparadigmen und insbesondere in die funktionale Programmierung, die nun mit JDK 8 Einzug in Java gehalten hat.

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 sowie Entwurfsmustern werden bei ihrer Verwendung in KAPITÄLCHEN dargestellt. Sourcecode-Listings sind in der Schrift courier gesetzt, um zu verdeutlichen, dass dieser Text einen Ausschnitt aus einem Java-Programm darstellt. Auch im normalen Text wird für Klassen, Methoden, Konstanten und Parameter diese Schriftart genutzt.

Verwendete Klassen aus dem JDK

Werden Klassen des JDKs zum ersten Mal im Text erwähnt, so wird deren voll qualifizierter Name, d. h. inklusive der Package-Struktur, angegeben: Für die Klasse String würde dann etwa java.lang.String notiert. Dies erleichtert eine Orientierung und ein Auffinden im JDK. Dies gilt insbesondere, da in den Listings nur selten import-Anweisungen abgebildet werden. Im nachfolgenden Text wird zur besseren Lesbarkeit auf diese Angabe verzichtet und nur der Klassenname genannt.

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

JDK

Java Development Kit

JLS

Java Language Specification

JRE

Java Runtime Environment

JSR

Java Specification Request

JVM

Java Virtual Machine

API

Application Programming Interface

ASCII

American Standard Code for Information Interchange

(G)UI

(Graphical) User Interface

IDE

Integrated Development Environment

XML

Extensible Markup Language

Danksagung

Bei der Erstellung des Manuskripts konnte ich auf ein starkes Team an Korrekturlesern zurückgreifen. Es ist mir eine große Freude, von den unterschiedlichen Sichtweisen und Erfahrungen vieler Leute profitieren zu dürfen.

Den einen oder anderen Tipp erhielt ich von Stefan Bartels, Tim Bötzmeyer, Sven Bremerstein, Philipp Dössegger, Peter Kehren, Dirk Lemmermann und Florian Messerschmidt. Insbesondere Merten Driemeyer und Dr. Carsten Kern haben mit verschiedenen hilfreichen Anmerkungen zu einer Verbesserung beigetragen. Außerdem danke ich Johannes Weigend für die hilfreichen Anregungen und Ergänzungsvorschläge sowie die Erlaubnis, seine Blog-Beiträge leicht abgewandelt nutzen zu dürfen.

Auch einige Kollegen meines Arbeitgebers Zühlke Engineering AG haben mich direkt oder indirekt unterstützt. Zunächst möchte ich meinem Chef Kai Schwidder für die Unterstützung und die Freiräume zur Ausgestaltung von internen Java-8-Kursen danken. Tatkräftig haben die Zühlkianer Wolfgang Giersche, Nikolaos Kaintantzis, Jörg Keller, Franziska Meyer, Sagi Nedunkanal und Christoph Süess durch ihre Kommentare zur Klarheit und Präzisierung beigetragen. Vielen Dank dafür!

Ein ganz besonderer Dank geht an Ralph Willenborg und Andreas Schöneck. Ralph hat mit seinen Adleraugen viele Tippfehler gefunden und sprachliche Verbesserungen angeregt. Andreas hat dieses Buch von seinen frühen Anfängen bis hin zum Endstadium durch viele Hinweise und Anregungen tatkräftig unterstützt. Vielen, vielen Dank für die Mühen und die schnellen Rückmeldungen auch zu später Stunde ;-)

Ebenso geht ein Dankeschön an das Team des dpunkt.verlags (Dr. Michael Barabas, Martin Wohlrab, Vanessa Wittmer 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 bei der Erstellung dieses Buchs zu Java 8 einen weit weniger gestressten Autor ertragen, als dies früher bei der Erstellung meines Buchs »Der Weg zum Java-Profi« der Fall war.

Anregungen und Kritik

Trotz großer Sorgfalt und mehrfachem Korrekturlesen lassen sich missverständliche Formulierungen oder sogar Fehler leider nicht vollständig ausschließen. Falls Ihnen etwas Derartiges auffällt, 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:

michael_inden@hotmail.com

Zürich und Aachen, im Mai 2014

Michael Inden

1 Einleitung

Am 18. März 2014 war es endlich so weit: Das zuvor mehrfach verschobene und lang erwartete Java 8 ist erschienen. Dieses Release enthält diverse wegweisende Erweiterungen und mit Lambda-Ausdrücken ein neues Sprachkonstrukt, das die funktionale Programmierung in Java erlaubt. Durch ein sorgfältiges API-Design von Massenoperationen auf Collections (Bulk Operations on Collections), wie Filterung, Transformation und Sortierung, lässt sich die funktionale gut mit der objektorientierten Programmierung verbinden. Dadurch ergeben sich vollkommen neue Gestaltungsmöglichkeiten, die jeder ambitionierte Java-Entwickler beherrschen sollte. Auch die lange Zeit stiefmütterlich behandelte Verarbeitung von Datumswerten wurde in Java 8 vollständig überarbeitet. Doch damit nicht genug: JavaFX als GUI-Technologie wurde aufpoliert. Darüber hinaus gibt es eine Vielzahl weiterer Funktionalitäten in Java 8 zu entdecken. Dieses Buch gibt einen Überblick über folgende wesentliche Erweiterungen in JDK 8:

Die aufgelisteten Themen stelle ich in jeweils separaten Kapiteln vor. Den Abschluss dieses Buchs bildet eine Beschreibung über bereits für JDK 7 vorgesehene Sprachfeatures, die es leider auch nicht in JDK 8 geschafft haben.

Entdeckungsreise JDK 8 – Wünsche an die Leser

Ich wünsche allen Lesern viel Freude mit diesem Buch zu Java 8 sowie einige neue Erkenntnisse und viel Spaß bei eigenen Experimenten mit JDK 8. Möge Ihnen der Umstieg auf Lambdas und die funktionale Programmierung mit meinem Buch ein wenig leichter fallen.

Lassen Sie uns nun die Entdeckungsreise durch JDK 8 und seine Neuerungen mit einem Einstieg in das Thema Lambda-Ausdrücke beginnen. Wenn Sie zunächst ein wenig mehr zu funktionaler Programmierung erfahren wollen, bietet sich ein Blick in den Anhang A an.

2 Lambda-Ausdrücke

Mit Lambda-Ausdrücken (kurz: Lambdas, zum Teil auch Closures genannt) wurde ein neues und von vielen Entwicklern heiß ersehntes Sprachkonstrukt in Java eingeführt, das bereits in ähnlicher Form in verschiedenen anderen Programmiersprachen wie C#, Groovy und Scala erfolgreich genutzt wird. Der Einsatz von Lambdas erfordert zum Teil eine andere Denkweise und führt zu einem neuen Programmierstil, der dem Paradigma der funktionalen Programmierung folgt. Mithilfe von Lambdas lassen sich einige Lösungen auf sehr elegante Art und Weise formulieren. Insbesondere im Bereich von Frameworks und zur Parallelverarbeitung kann die Verwendung von Lambdas enorme Vorteile bringen. Diverse Funktionalitäten im Collections-Framework und an anderen Stellen des JDKs wurden auf Lambdas umgestellt. Bevor wir darauf zurückkommen, schauen wir uns zunächst einmal Lambdas an sich an.

Beispiel: Sortierung nach Länge und kommaseparierte Aufbereitung

Um die Vorteile von Lambdas und auch später von den sogenannten Bulk Operations on Collections besser nachvollziehen zu können, betrachten wir als praxisnahes Beispiel eine Liste von Namen. Diese Namen wollen wir nach deren Länge sortieren und die Längen danach kommasepariert ausgeben. Dazu würden wir bis einschließlich JDK 7 in etwa folgenden Sourcecode schreiben:

// Sortierung mit Comparator
final List<String> names = Arrays.asList("Andy", "Michael", "Max", "Stefan");
Collections.sort(names, new Comparator<String>()
{
    @Override
    public int compare(final String str1, final String str2)
    {
        return Integer.compare(str1.length(), str2.length());
    }
});

// Iteration und Ausgabe
final Iterator<String> it = names.iterator();
while (it.hasNext())
{
    System.out.print(it.next().length() + ", "); // 3, 4, 6, 7,
}

Beim Betrachten dieser Umsetzung kann man sich fragen, ob das nicht kürzer und einfacher gehen sollte? Die Antwort ist: Ja, mit JDK 8 kann man dazu Lambdas nutzen.

2.1 Einstieg in Lambdas

Das Sprachkonstrukt Lambda kommt aus der funktionalen Programmierung. Ein Lambda ist ein Behälter für Sourcecode ähnlich einer Methode, allerdings ohne Namen und ohne die explizite Angabe eines Rückgabetyps oder ausgelöster Exceptions. Vereinfacht ausgedrückt kann man einen Lambda am ehesten als anonyme Methode mit folgender Syntax und spezieller Kurzschreibweise auffassen:

(Parameter-Liste) -> { Ausdruck oder Anweisungen }

2.1.1 Lambdas am Beispiel

Ein paar recht einfache Beispiele für Lambdas sind die Addition von zwei Zahlen vom Typ int, die Multiplikation eines long-Werts mit dem Faktor 2 oder eine parameterlose Funktion zur Ausgabe eines Textes auf der Konsole. Diese Aktionen kann man als Lambdas wie folgt schreiben:

(int x, int y) -> { return x + y; }
(long x) -> { return x * 2; }
() -> { String msg = "Lambda"; System.out.println("Hello " + msg); }

Das sieht recht unspektakulär aus, und insbesondere wird klar, dass ein Lambda lediglich ein Stück ausführbarer Sourcecode ist, der

Lambdas im Java-Typsystem

Wir haben bisher gesehen, dass sich einfache Berechnungen mithilfe von Lambdas ausdrücken lassen. Wie können wir diese aber nutzen und aufrufen? Versuchen wir zunächst, einen Lambda einer java.lang.Object-Referenz zuzuweisen, so wie wir es mit jedem anderen Objekt in Java auch tun können:

// Compile-Error: incompatible types: Object is not a functional interface
Object greeter = () -> { System.out.println("Hello Lambda"); };

Die gezeigte Zuweisung ist nicht erlaubt und führt zu einem Kompilierfehler. Die Fehlermeldung gibt einen Hinweis auf inkompatible Typen und verweist darauf, dass Object kein Functional Interface ist. Aber was ist denn ein Functional Interface?

2.1.2 Functional Interfaces und SAM-Typen

Ein Functional Interface ist eine neue Art von Typ, die mit JDK 8 eingeführt wurde, und repräsentiert ein Interface mit genau einer abstrakten Methode. Ein solches wird auch SAM-Typ genannt, wobei SAM für Single Abstract Method steht. Diese Art von Interfaces gibt es nicht erst seit Java 8 im JDK, sondern schon seit Langem und vielfach – wobei es früher für sie aber keine Bezeichnung gab. Vertreter der SAM-Typen und Functional Interfaces sind etwa Runnable, Callable<V>, Comparator<T>, FileFilter, FilenameFilter, ActionListener, EventHandler usw.

@FunctionalInterface
public interface Runnable
{
   public abstract void run();
}
@FunctionalInterface
public interface Comparator<T>
{
   int compare(T o1, T o2);
   boolean equals(Object obj);
}

Im Listing sehen wir die mit JDK 8 eingeführte Annotation @FunctionalInterface aus dem Package java.lang. Damit wird ein Interface explizit als Functional Interface gekennzeichnet. Die Angabe der Annotation ist optional: Jedes Interface mit genau nur einer abstrakten Methode (SAM-Typ) stellt auch ohne explizite Kennzeichnung ein Functional Interface dar. Wenn die Annotation angegeben wird, kann der Compiler eine Fehlermeldung produzieren, falls es (versehentlich) mehrere abstrakte Methoden gibt.

Implementierung von Functional Interfaces

Herkömmlicherweise wird ein SAM-Typ bzw. Functional Interface durch eine anonyme innere Klasse implementiert. Seit JDK 8 kann man alternativ zu dessen Implementierung auch Lambdas nutzen. Voraussetzung dafür ist, dass das Lambda die abstrakte Methode des Functional Interface erfüllen kann, d. h., dass die Anzahl der Parameter übereinstimmt sowie deren Typen und der Rückgabetyp kompatibel sind. Schauen wir zur Verdeutlichung zunächst auf ein allgemeines, etwas abstraktes Modell zur Transformation von bisherigen Realisierungen eines SAM-Typs mithilfe einer anonymen inneren Klasse in einen Lambda-Ausdruck:

// SAM-Typ als anonyme innere Klasse
new SAMTypeAnonymousClass()
{
    public void samTypeMethod(METHOD-PARAMETERS)
    {
        METHOD-BODY
    }
}

// SAM-Typ als Lambda
(METHOD-PARAMETERS) -> { METHOD-BODY }

Bei kurzen Methodenimplementierungen, wie sie für SAM-Typen häufig vorkommen, ist das Verhältnis von Nutzcode zu Boilerplate-Code (oder auch Noise genannt) bislang recht schlecht. Wenn man für derartige Realisierungen Lambdas einsetzt, so kann man mit einer Zeile das ausdrücken, was sonst fünf oder mehr Zeilen benötigt. Nachfolgend wird dies für die Interfaces Runnable und Comparator<T> verdeutlicht.

Beispiel 1: Runnable Konkretisieren wir die allgemeine Transformation anhand eines java.lang.Runnable, das eine triviale Konsolenausgabe implementiert:

Runnable runnableAsNormalMethod = new Runnable()
{
    @Override
    public void run()
    {
        System.out.println("runnable as normal method");
    }
}

In diesem Runnable wird keine wirklich sinnvolle Funktionalität realisiert. Vielmehr dient dies nur der Verdeutlichung der Kurzschreibweise mit einem Lambda wie folgt:

Runnable runnableAsLambda = () -> System.out.println("runnable as lambda");

Beispiel 2: Comparator<T> Die Vorteile von Lambdas lassen sich für das Functional Interface Comparator<T> prägnanter zeigen. Ich möchte kurz in Erinnerung rufen, dass mit einem Komparator ein Vergleich von zwei Instanzen vom Typ T realisiert wird. Dazu muss die abstrakte Methode int compare(T, T) passend implementiert werden und über ihren Rückgabewert die Reihenfolge der Werte ausdrücken (vgl. folgenden Praxishinweis). Wollte man zwei Strings nach deren Länge sortieren, so entsteht herkömmlicherweise recht viel Sourcecode:

// Hinweis: Diamond Operator ist nicht für anonyme innere Klassen möglich
Comparator<String> compareByLength = new Comparator<String>()
{
    @Override
    public int compare(final String str1, final String str2)
    {
        final int length1 = str1.length();
        final int length2 = str2.length();

        if (length1 < length2)
            return -1;
        if (length1 > length2)
            return 1;

        return 0;
    }
};

Mit JDK 7 wurde die Klasse Integer um eine Methode compare(int, int) erweitert, die einen komparatorkonformen Rückgabewert liefert und so die Implementierung deutlich vereinfacht und verkürzt:

Comparator<String> compareByLength = new Comparator<String>()
{
    @Override
    public int compare(final String str1, final String str2)
    {
        return Integer.compare(str1.length(), str2.length());
    }
};

Wenn man Lambdas nutzt, lässt sich der Komparator knackig wie folgt schreiben:

Comparator<String> compareByLength = (final String str1, final String str2) ->
{
    return Integer.compare(str1.length(), str2.length());
};

2.1.3 Type Inference und Kurzformen der Syntax

Die Syntax von Lambdas besitzt einige Besonderheiten, um den Sourcecode recht knapp formulieren zu können. Dabei nutzt man für Lambdas insbesondere auch die sogenannte Type Inference: Durch Verbesserungen der Type Inference im Compiler ist es ähnlich wie beim Diamond Operator bei der Definition generischer Klassen für Lambdas möglich, auf die Angabe der Typen für die Parameter im Sourcecode zu verzichten. Dazu ermittelt der Compiler die passenden Typen aus dem Einsatzkontext. Den vorherigen Komparator schreibt man ohne Typangabe wie folgt:

Comparator<String> compareByLength = (str1, str2) ->
{
    return Integer.compare(str1.length(), str2.length());
};

Eine weitere Verkürzung in der Schreibweise eines Lambdas kann man durch folgende Regeln erzielen: Falls das auszuführende Stück Sourcecode ein Ausdruck ist, können die geschweiften Klammern um die Anweisungen entfallen. Ebenfalls kann dann das Schlüsselwort return weggelassen werden und der Rückgabewert entspricht dem Ergebnis des Ausdrucks. Außerdem gilt: Existiert lediglich ein Eingabeparameter, so sind die runden Klammern um den Parameter optional. Damit ergibt sich für die Ausdrücke

(int x, int y) -> { return x + y; }
(long x) -> { return x * 2; }

folgende Kurzschreibweise:

(x, y) -> x + y
x -> x * 2

Neben dem offensichtlichen Vorteil einer recht kompakten Schreibweise, ist etwas anderes viel entscheidender: Lambdas können flexibler als streng typisierte Methoden genutzt werden. Für die gezeigten Berechnungen ist ein Einsatz überall dort möglich, wo für die Parameter die Operatoren + bzw. * definiert sind, also für die Typen int, float, double usw. Anders formuliert: Alles, was hergeleitet werden kann (und soll), darf in der Syntax weggelassen werden. Als Beispiel betrachten wir folgende ActionListener-Implementierung, die schrittweise vereinfacht wird:

// Alter Stil
button.addActionListener(new ActionListener()
{
    @Override
    public void actionPerformed(final ActionEvent e)
    {
        System.out.println("button clicked (old way)");
    }
});

Diese herkömmliche Realisierung mithilfe einer anonymen inneren Klasse lässt sich als Lambda und mit Type Inference deutlich kürzer schreiben:

// Lambda-Variante mit Type Inference
button.addActionListener( (e) -> { System.out.println("button clicked!"); } );

Nutzt man zusätzlich die Regeln zur Schreibweisenabkürzung, so entsteht Folgendes:

// Lambda-Kurzschreibweise
button.addActionListener( e -> System.out.println("button clicked!") );

2.1.4 Lambdas als Parameter und als Rückgabewerte

Wir haben mittlerweile ein wenig Gespür für Lambdas gewonnen und wissen, dass man Lambdas anstelle einer anonymen inneren Klasse zur Realisierung eines SAM-Typs nutzen kann. Ebenso kann man Lambdas auch als Methodenparameter und als Rückgabe einer Methode verwenden, um Aufrufe lesbar zu gestalten.

Wir greifen das Beispiel aus der Einleitung wieder auf und betrachten das Sortieren einer Liste von Namen gemäß deren Länge. Das können wir mit folgenden zwei Varianten eines Lambdas für das Interface Comparator<T> schreiben: