Java 8 Streams: Grundlagen und Motivation

Eine der großen Neuerungen von Java 8 ist die neue Streams API (Package java.util.stream). Ein Stream im Sinne dieser API ist einfach eine Folge von Elementen. So ein Stream kann aus den Elementen einer Collection bestehen, die Elemente können aber auch von einer anderen Quelle in den Stream gelesen werden.

Inhaltsverzeichnis

Die API bietet eine Menge an Operationen um Elemente zu verarbeiten für die davor eine Abarbeitung mit Schleifen notwendig war.

Motivation für die Verwendung von Streams

Um über die Elemente einer Liste zu iterieren wird meistens eine for-Schleife in der foreach-Notation verwendet. Die meisten Programme enthalten viele Codestellen wie die folgende:

List<String> list = Arrays.asList("BrewAge", "1516", "Baumgartner");
for (String item:list){
    System.out.println(item.length());
}

Jedes mal wenn jedes Element einer Liste verarbeitet werden soll, muss so eine Schleife geschrieben werden. Die Streams API bietet verschiedene Möglichkeiten an, die Elemente zu verarbeiten und Funktionen auf diese Elemente anzuwenden. Das Stream-Gegenstück zu einer Schleife ist die Methode forEach().

list.stream().forEach(new Consumer<String>() {
    @Override
    public void accept(String item) {
        System.out.println(item.length());
    }
});

Die Methode stream() am Interface java.util.Collection erstellt einen Stream aus der Liste. Die Stream-Methode forEach() nimmt ein Objekt vom Typ Consumer entgegen, der nur eine Methode besitzt, nämlich accept(String item). Diese Methode wird für jedes Element des Streams aufgerufen.

Die Ausgabe ist bei beiden Varianten die gleiche:

7
4
11

Streams und Lamda-Ausdrücke

Die Variante mit dem Stream hat in der obigen Form kaum einen Vorteil: der Code scheint länger und komplexer als die einfache Lösung mit der Schleife. Gottseidank bietet Java 8 für diesen Code eine neue Lösung: Lambda Expressions, also Funktionsausdrücke.

Die accept() Methode nimmt ein Objekt von einem gewissen Typ entgegen und hat keinen Rückgabewert. Mit so einem Lambda-Ausdruck kann der Code wie folgt vereinfacht werden:

list.stream().forEach((String item) -> System.out.println(item.length()) );

Da der Java-Compiler den Typ der Funktion aus dem generischen Typ der liste (`List<String>) abgeleitet werden kann, kann der Ausdruck noch weiter vereinfacht werden:

list.stream().forEach(item -> System.out.println(item.length()) );

Da die Methode System.out.println() die selbe Signatur (ein Parameter vom generischen Typ der Liste, kein Rückgabewert) wie die accept()-Methode hat, kann auch gleich eine Referenz auf die Methode übergeben werden:

list.stream().forEach(System.out::println);

Diese Version ist nun wirklich kürzer und nach einiger Zeit und Übung mit Lambda-Expressions auch leichter zu lesen.

Method-Chaining mit Streams

Einige Methoden des Stream Interfaces geben selbst wieder einen Stream zurück. So ist es möglich, Elemente in Streams zu filtern oder zu modifizieren:

List<Integer> lengthList = list.stream()
    .filter(item -> item.length() > 4)
    .map(String::length)
    .collect(Collectors.toList());

In diesem Beispiel werden zuerst alle Elemente die länger als 4 sind herausgefiltert. Dann werden die Längen berechnet. Die Methode map() wandelt mit der Funktion String::length den String in eine Zahl (die Länge des Strings) um und gibt einen Stream vom Typ Integer zurück. Mit der Methode collect() werden die Werte des Integer-Streams wieder in eine Liste umgewandelt. Der Inhalt der Liste sieht nun so aus:

[7, 11] 

Ähnliche Artikel