create mobile friendly website
Lambda Expressions Collections

 

Java 8 - Die neue Version (Teil III)

Lambda Expressions & Collections

Nachdem wir Ihnen im zweiten Teil unserer Reihe zum Thema Java 8 [2] einen kurzen Einblick in die neuen Lambda Expressions gegeben haben, werden wir in diesem Teil die neuen Funktionen im Zusammenhang mit Collections vorstellen.

Collections sind Klassen, die Objekte in Gruppen zusammenfassen. Dazu zählen Listen und Sets. Lambda Expressions erweitern das Collection Framework und bieten damit neue Möglichkeiten im Umgang mit diesen. Dabei steht die Vereinfachung der Lesbarkeit des Programmcodes sowie die Optimierung durch Parallelisierung im Vordergrund.

Ermöglicht wird dies durch eine Reihe von neuen Funk­tionen, die direkt auf eine Collection angewendet werden können. So ist zum Beispiel das Durchlaufen und Verar­beiten einer Liste mit nur einer Zeile Code möglich. Daneben gibt es weitere Funktionen, wie das Filtern oder die Verarbeitung von Attributen der Elemente innerhalb einer Collection. Diese werden wir Ihnen im Folgenden vorstellen und erläutern.

Durchlaufen einer Collection mittels forEach

Die Erweiterung des Collection Framework ermöglicht, wie bereits erwähnt, das direkte Durchlaufen der Elemente innerhalb einer Collection. Hierfür wird die neue Methode forEach() verwendet.

Die Abbildung 1 zeigt, dass das Durchlaufen einer Collection auf zwei Arten möglich ist: In der ersten Variante wird zunächst ein Synonym (Variablenname) für das aktuelle Objekt vergeben. Damit kann auf das Objekt zugegriffen werden. Hierbei handelt es sich um die Standardsyntax der Lambda Expression. Diese Variante hat den Vorteil, dass mehrere Aktionen in einem Schleifendurchlauf ausgeführt werden können. Der Lambda-Ausdruck ersetzt hier einfach die Implementierung des Consumer-Interface, das die Methode forEach() als Parameter erwartet.

Es gilt jedoch zu beachten, dass innerhalb der Schleife auf Variablen des umliegenden Blocks nur lesend zugegriffen werden kann. Dazu werden die Variablen implizit als final deklariert. Bei der zweiten Variante des Durchlaufens handelt es sich um eine Methodenreferenz. Hierbei wird direkt eine Methode des Objektes aufgerufen und ausgeführt. In dem Beispiel wird die Methode printName() der Klasse Person ausgeführt, welche den Namen auf der Standardkonsole ausgibt.

Verarbeitung von Collections mit Streams

Bei der Verarbeitung von Collections mit Java 8 stellt das neue Interface java.util.stream.Stream die wesent­lichen Funktionen bereit. Dazu wurde das Collection Interface um die Methoden stream() und parallelStream() erweitert, welche einen entsprechenden Stream liefern. Dadurch wird die einfache Verwendung folgender Funktionalitäten in Java möglich:

  • filter
  • map
  • reduce

Diese Funktionen erfüllen unterschiedliche Aufgaben. Mittels der filter-Funktionen können die Einträge einer Collection anhand individueller Kriterien eingegrenzt werden. Mit Hilfe der map-Funktionen werden Einträge einer Collection in einen anderen Datentyp umgewandelt und mit den reduce-Funk­tionen lassen sich die Eingangswerte auf einen konkreten Wert oder eine neue Wertmenge reduzieren.

filter-Funktionen

Neben dem reinen Durchlaufen einer Collection können die zu durchlaufenden Einträge auch vorher mittels Predicates gefiltert werden. Ein Predicate ist ein Objekt, welches eine Bedingung auf ein Argument prüft und true oder false zurückliefert. Um den Filter zu nutzen, arbeiten wir mit der Möglichkeit, Funktionen aneinander zu ketten. Außerdem wird eine Predicate-Instanz benötigt, in der die Suchparameter festlegt werden.

Die Abbildung 2 zeigt die beispielhafte Anwendung eines Filters. Wir definieren zunächst ein Predicate, welches die Bedingung zum Filtern enthält. Anschließend können wir dieses Objekt der neuen filter-Funktion übergeben. Die Methode forEach() operiert anschließend auf der gefilterten Datenmenge. Es wird also für jedes Element, das nach der Filterung zur Verfügung steht, die Methode printName() aufgerufen.

map-Funktionen    

Mit Hilfe der map-Funktionen kann ein neuer Stream aus dem Quellstream erzeugt werden. Dazu ist wieder das Öffnen eines Stream notwendig. map-Funktionen lassen sich in verschiedenen Arten von Streams anwenden:

  • map()
  • mapToInt()
  • mapToDouble()
  • mapToLong()

Neben der Anwendung in Int-, Double- und LongStreams ist auch das Abbilden in Streams mit beliebigen Datentypen möglich.

In der Abbildung 3 wird gezeigt, wie bereits mit einer einzigen Programmanweisung aus dem Quellstream mittels mapToInt() zunächst ein IntStream erzeugt werden kann und davon dann die forEach()-Methode aufgerufen wird, um jedes Alter auszugeben.

Das zweite Beispiel der Abbildung 3 nutzt die allgemeine map()-Methode, welche als Parameter die Implementierung eines Function-Interface erwartet. Hier wird eine Methoden­referenz verwendet, um von jedem Person-Objekt den Nachnamen zu erhalten. Die Methode map() liefert in diesem Falle einen Stream vom Typ String zurück.

reduce-Funktionen

Zu den reduce-Funktionen zählt beispielsweise die
Methode collect(). Während der Stream-Verarbeitung können wir keine Änderung der Elemente in der Collection vornehmen. Wir haben jedoch die Möglichkeit z.B. gefilterte Elemente in einer neuen Collection zu speichern. Dazu öffnen wir einen Stream, wenden einen Filter an und rufen im Anschluss die Funktion collect() auf, der wir die Opera­tion Collectors.toList() übergeben. Dadurch er­halten wir als Rückgabeparameter eine neue Collection. Die Abbildung 4 verdeutlicht dies in einem Beispiel.

Neben collect() gibt es noch weitere reduce-Funk­tionen. Mit diesen lassen sich die Elemente einer Collection auf ein konkretes Ergebnis einschränken.

In der Abbildung 5 ist dargestellt, wie wir das Durch­schnittsalter von Personen in der Collection berechnen können. Um die Werte zu definieren, die für die Berechnung des Durchschnitts verwendet werden sollen, nutzen wir eine der bereits vorgestellten map-Funktionen. Die Methode average() stellt in diesem Beispiel die eigentliche reduce-Funktionalität zur Verfügung. Aus dem Alter der Personen wird damit das Durchschnittsalter berechnet.

Für den Fall, dass die Collection zu diesem Zeitpunkt keine Einträge enthält, wurde die OptionalDouble-Klasse eingeführt. Diese kann ein Double enthalten oder leer sein und bietet erweiterte Funktionalitäten, um eine NullPointerException zu verhindern. Die Methode isPresent() der Klasse OptionalDouble liefert ein false zurück falls diese keinen Wert enthält.

Weitere reduce-Funktionen sind zum Beispiel:

  • Berechnung der Anzahl der enthaltenen Elemente: sum()
  • Ermittlung des Maximal- oder Minimalwertes: min(), max()
  • Individuelle Berechnungen:reduce(...)

Intermediate und Terminal

Der bereits beschriebene Stream ist vergleichbar mit der View einer Datenbank. Der Stream selbst enthält keine Daten sondern stellt lediglich eine Sicht auf einen Ausschnitt von Daten einer Collection zur Verfügung.

Solange die Methoden, die auf eine Collection angewendet werden, einen Stream zurückliefern, wird noch keine Verarbeitung angestoßen. Diese Zwischenschritte zur Stream-Erzeugung werden als intermediate bezeichnet und können als Zwischenprodukt oder auch als Erstellung einer View angesehen werden. Erst wenn die Daten verarbeitet werden, zum Beispiel durch die forEach()-Methode, erfolgt die Ausführung der intermediates und die anschließende Verarbeitung. Dies wird als terminal-Operation bezeichnet (Quelle [3]).

Abbildung 6 zeigt, wie die Nachnamen einer gefilterten Collection mittels System.out ausgegeben werden. Die Methoden filter() und map() werden zwar aufgerufen, die eigentliche Funktionalität dahinter wird aber „lazy“ ausgeführt, wenn der terminal, in diesem Fall die forEach()-Methode, aufgerufen wird.

In diesem Zusammenhang spricht man auch von Laziness da nur die Daten verarbeitet werden, die auch wirklich be­nötigt werden und durch die intermediate-Funktionen bereitgestellt werden.

Parallelisierung

Wie bereits kurz angesprochen, ermöglichen die neuen Collection-Funktionalitäten eine Parallelisierung der Verarbeitung. Bisher haben wir als Java-Entwickler in der Regel externe Iterationen zum Durchlaufen von Collections verwendet. Damit wurde mit Hilfe eines Iterators explizit auf jedes Element sequentiell zugegriffen.

Die neuen Funktionen (z.B. forEach()) greifen hingegen auf eine interne Iteration zurück. Dies hat den Vorteil, dass man sich als Entwickler nicht um die konkrete Art und Weise der Iteration kümmern muss. Dadurch ist es relativ einfach, diese Iterationen zu parallelisieren. Dieser Mechanismus wird in der Ab­bildung 6 durch Aufruf der Methode parallelStream() verwendet. Sämtliche Operationen werden dadurch intern mit Hilfe des in Java 7 eingeführten Fork-Join-Framework verarbeitet.

Ob eine parallele Verarbeitung der Daten sinnvoll und tatsächlich performanter ist, sollte unbedingt durch entsprechende Messungen überprüft werden. Denn immerhin bringt die gewonnene Parallelität einen zusätzlichen Synchronisierungsaufwand mit sich.

Fazit und Ausblick

Die Handhabung von Collections wurde mit Hilfe der Lambda Expressions deutlich vereinfacht. Neben dem besser lesbaren und kürzeren Programmcode gibt es jetzt Komfortfunktionen, welche die Programmierung vereinfachen. Vor allem die einfache Verwendung der parallelen Datenverarbeitung punktet enorm. Sicherlich ist auch an dieser Stelle eine Eingewöhnung notwendig, dennoch überzeugt die neue Syntax bereits nach kurzer Zeit.

 

Dominik Löhr ()

 

Links

[1] ORDIX® news Artikel 1/2014 „Java 8 - Die neue Version (Teil I) - Ein erster Überblick“: http://www.ordix.de/ordixnews/ordix-news-archiv/12014.html

[2] ORDIX® news Artikel 2/2014 „Java 8 - Die neue Version (Teil II)- Lambda Expressions“: http://www.ordix.de/ordixnews/ordix-news-archiv/2-2014.html

 

Quellen

[1] Lambda-Expressions: http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

[2] IntStream: http://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html

[3] Package java.util.stream: http://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

 

Bildnachweis

© dryicons | Promotion Banners
© freepik.com | Business meeting vector template

ORDIX aktuell

Daten mit Format – Lösungen zur Datenablage in Hadoop
Durch das verteilte Dateisystem HDFS ermöglicht Hadoop die zuverlässige Ablage großer Datenmengen sowie die effiziente A...
Weiterlesen ...
Lesestoff zum Weihnachtsfest: ORDIX®   news 2/2016
​Die aktuelle ORDIX® news 2/2016 hält zum Ende des Jahres wieder interessante Artikel rund um die IT bereit. Ob Big Data...
Weiterlesen ...
ORDIX auf der DOAG Konferenz und beim Schulungstag
Gefüllte Zuhörerplätze bei unseren Vorträgen und höchste Teilnehmerzahlen beim Schulungstag zeugen vom Erfolg und der Be...
Weiterlesen ...

Kostenloses Kundenmagazin

Abbildungen

Die vollständigen Artikel mit allen Abbildungen finden Sie im blätterbaren PDF.
Eine Übersicht über alle Artikel sowie das PDF zum Download finden Sie im Inhaltsverzeichnis.

Impressum

Impressum der ORDIX® news 3/2014

Links

Reihe "Java 8 - Die neue Version"
[1] Teil I „Ein erster Überblick“
[2] Teil II „Lambda Expressions“

Quellen

[1] Lambda-Expressions
[2] IntStream
[3] Package java.util.stream

Bildnachweis

© dryicons | Promotion Banners
© freepik.com | Business meeting vector template