Overview
Ein Stream in Java ist eine Daten-Sequenz, auf die Operationen wie Filtern, Sortieren oder Transformieren funktional angewendet werden können. Wir können uns einen Stream wie eine Art Fließand in einer Fabrik vorstellen, auf dem ebenfalls unterschiedliche Verarbeitungsschritte nacheinander abgearbeitet werden.
In der Vergangenheit haben wir bereits regelmäßig mit Datensammlungen aus dem Collections-Framework gearbeitet, wobei wir unsere Aufgaben objektorientiert gelöst haben.
Gerade bei sehr großen Datenmengen (Big Data) ist es aber entscheidend, entsprechende Programmieraufgaben möglichst effizient, elegant und performant lösen zu können. Hier kommt das neu einführte Konzept der Streams in Spiel. Damit nämlich lässt sich die Massendatenverarbeitung auf Collections (Bulk Operations on Collections) mit Mustern der der funktionalen Programmierung lösen.
Der Performance-Vorteil von Streams gegenüber der traditionellen (objektorientierten) Arbeit mit Collections kommt daher, da Streams kaum bzw. keine Daten zwischenspeichern und dadurch deutlich weniger Speicher verbrauchen.
Grundlage für Streams ist das Interface java.util.stream.Stream<T>. Doch was genau ist ein Stream?
Manche vergleichen Streams mit der Abarbeitung am Fließband, und das ergibt durchaus Sinn. Grafisch lässt sich das Konzept der Streams so darstellen:
Es gibt drei Arten von Operationen:
Intermediate Operations sind als "Black Box" zu sehen. Wir interessieren uns nur dafür, was am Ende rauskommt. Was innerhalb der einzelnen Operationen passiert, ist ein Detail der Implementierung.
Das Stream Interface befindet sich im java.util.stream Paket. Mittels der Methode steam() lässt sich aus einer Collection ein Stream-Objekt erstellen:
List<String> list = Arrays.asList("Wolf", "Tiger", "Drache");
Stream<String> stream = list.stream(); // sequentieller Stream
In unserem Beispiel haben wir einen sequentiellen Stream aus der Liste erzeugt. Wir können jedoch mittels der Methode parallelStream() alternativ auch einen parallelen Stream erstellen:
Stream<String> stream = list.parallelStream(); // paralleler Stream
Ein Stream in Parallelverarbeitung hat den Vorteil, dass die Elemente parallel verarbeitet werden: Es ist so, als würden auf unserem Fließband Arbeitsschritte für mehrere Elemente nicht sequentiell (nacheinander), sondern gleichzeitig durchgeführt werden. Gerade bei großen Streams kann das einen erheblichen Performance-Gewinn bedeuten.
Wir sollten für kleinere Streams aber die sequentielle "Standard"-Variante verwenden. Sequentielle Streams haben nämlich in der Regel weniger Overhead Cost, da keine zusätzliche Rechenleistung für die Koordinierung der Paralellverarbeitung erforderlich ist. Mit anderen Worten: Kleine Streams laufen sequentiell schneller.
Da wir nun wissen, was ein Stream ist und wie wir ihn erstellen, wollen wir uns in zwei einfachen Beispiel mit der Benutzung vertraut machen.
List<Integer> numbers = Arrays.asList(2, 3, 4, 5, 6);
int summe = numbers.stream().filter(p -> p % 2 == 0).mapToInt(p->p).sum(); // 12
Als Datenquelle für unseren Stream dient eine Liste aus ganzen Zahlen. Als Ergebnis möchten wir die Summe aller geraden Zahlen der Liste in einer int-Variable speichern.
Im nächsten Beispiel schauen wir uns einen weiteren Stream an, der wieder mit der Methode stream aus einer Liste von Player-Objekten erzeugt wird. Im Chaining wird der Stream dann mit der Methode mehrAls100Punkte gefiltert wird und anschließend wieder als List<Player> zurückgegeben:
List<Player> guteSpieler = spieler.stream(). // Create
filter(Player::mehrAls100Punkte). // Intermediate
collect(Collectors.toList()); // Terminal
Wir haben gesehen, wie effizient die Verkettung von Stream-Operationen (Intermediate Operations) ist und um wieviel klarer unser Code wird. Lösungen mit Streams sind ein wunderbares Beispiel dafür, wie sinnvoll das Konzept des "Was" (funktionale Programmierung) gegenüber dem "Wie" sein kann.
Im nächsten Tutorial gehen wir dann genauer auf die einzelnen Intermediate und Terminal Operations ein.
Java Basics
[Java einrichten] [Variablen] [Primitive Datentypen] [Operatoren] [if else] [switch-case] [Arrays] [Schleifen]
Objektorientierung
[Einstieg] [Variablen ] [Konstruktor] [Methoden] [Rekursion] [Statische Member] [Initializer] [Pass-by-value] [Objektsammlungen] [Objektinteraktion] [Objekte löschen]
Klassenbibliothek
[Allgemeines] [String ] [Math] [Wrapper] [Scanner] [java.util.Arrays] [Date-Time-API]
Vererbung
[Einstieg Vererbung] [Konstruktoren bei Vererbung ] [Der protected Zugriffsmodifikator] [Abstrakte Klassen und Methoden] [Polymorphie in Java] [Typumwandlung] [Die Klasse Object] [Die toString()-Methode] [Objekte vergleichen] [Was ist ein Interface?]