Java 8 – Stream API (Teil 1)

Einführung in das Stream API von Java 8

In Java 8 sind mit dem Stream API Erweiterungen für die Arbeit mit Collections aufgenommen worden. Streams erleichtern insbesondere parallele Zugriffe auf Collections, sind aber auch für sequentielle Zugriffe sehr nützlich.

Zum Verständnis des Beitrags sind Kenntnisse über Lambda-Ausdrücke und Methodenreferenzen hilfreich.

Einfache Stream-Operationen

Die Erstellung eines sequentiellen oder parallelen Streams aus einer Collection ist sehr einfach:

Der forEach-Methode des Streams wird als Lambda-Ausdruck die Funktionalität übergeben, die auf jedes Element der Liste angewandt werden soll. Ein Listenelement wird mit der Variable word angesprochen, deren Typ String automatisch erkannt wird.

Die filter-Methode erzeugt aus einem Stream einen neuen mit Elementen, die einer bestimmten Bedingung entsprechen:

Die map-Methode wandelt Elemente mit Hilfe der übergebenen Funktion um. Ergebnis kann auch ein anders typisierter Stream sein:

Die Elemente einer Liste können per reduce auf ein einzelnes Element reduziert werden. Die reduce-Methode erhält als Parameter den Initialwert und den Operator zur Akkumulation der Elemente.

(Die Verkettungen per + sind nur ein einfaches Beispiel, es gibt sicher elegantere Lösungen.)

Abarbeitung im Stream

Intermediäre Stream-Operationen geben Streams zurück (z. B. filter, map). Sie werden verzögert ausgeführt. Terminale Stream-Operationen beenden die Kette von Streams (z. B. forEach, reduce) und werden sofort ausgeführt.

Die Abarbeitung eines Streams startet erst bei Aufruf der terminalen Operation. Der Algorithmus wendet nacheinander auf jedes Element die Kette der Operationen bis zur terminalen Operation an. Im folgenden Beispiel wird also zunächst für ein Element die Bedingung ausgewertet, im Erfolgsfall die Länge ermittelt und die Bildung der Summe gestartet. Das wird für jedes weitere Element wiederholt. Es wird nicht ein Stream aus den gefilterten Elementen erstellt, dann ein Stream aus allen Wortlängen und daraus die Summe gebildet. Weil es keine Zwischenergebnisse gibt, müssen diese bei Parallelverarbeitung auch nicht synchronisiert werden.

Ein Stream speichert keine Elemente, sondern enthält nur Verweise auf eine Collection oder ein Array, zusammen mit einer Liste von unerledigten (intermediären) Operationen. Die terminale Operation stößt die Verarbeitung aller Operationen an und konsumiert den Stream. Eine zweite terminale Operation auf einem Stream ist nicht möglich.

Mit Hilfe der peek-Methode können Zwischenergebnisse ausgegeben werden, ohne den Stream zu konsumieren und ihn damit zu schließen. Die Elemente werden danach an die nachfolgende Operation weitergereicht. Mit folgendem Code wird die Reihenfolge der Abarbeitung der Operationen sichtbar:

Das Ergebnis dieses Streams ist übrigens nicht 8, sondern ein Optional mit dem Wert 8. Der Optional-Datentyp ist ein Container für einen beliebigen Wert. Wenn die Filter-Operation in diesem Fall keine Elemente geliefert hätte, wäre die Summierung der Längen nicht durchgeführt worden. Das Ergebnis wäre nicht null, sondern eine leere Optional-Instanz.

Quelle: Java Magazin 6 | 2014

Verfasst von Marion Wilker am 3. Februar 2017