Motivation
Das grundlegende Problem
- Collections erlauben Objektmengen zu verwalten
- Bearbeiten von Collections erfolgt über externe Iteration (external iteration)
- fehleranfällig
- unübersichtlich
- nicht parallelisierbar
List<String> namen = Arrays.asList("Franz", "Peter", "Jakob", "Hans");
Iterator<String> it = namen.iterator();
while (it.hasNext()) {
String name = it.next();
System.out.println(name);
}
Externe Iteration ist ein typisches Vorgehen bei imperativen Programmiersprachen. Das Problem liegt darin, dass der Quelltext relativ weit von der Absicht (Intention) entfernt ist. Im vorliegenden Beispiel ist die Intention: „Gib den Wert aller Elemente der Collection aus“. Diese Absicht geht aber im Quelltext weitgehend unter und wird durch eine Kontrollstruktur und zwei temporäre Variablen verschleiert.
Man kann das Beispiel mithilfe der foreach-Loop noch etwas kompakter schreiben, das grundlegende Problem bleibt aber.
List<String> namen = Arrays.asList("Franz", "Peter", "Jakob", "Hans");
for (String name : namen) {
System.out.println(name);
}
Streams
- Java 8 führt eine neue Art ein, mit Collections zu arbeiten: Streams
- Über die Methode
stream()
kann man zu jeder Collection einen Stream erhalten - Streams folgenden den Prinzipien der Funktionalen Programmierung, d. h. man teilt dem Stream über eine Funktion mit, was geschehen soll
- Streams erlauben eine interne Iteration (internal iteration)
- Filtern von Einträgen
- Zusammenführen von Collections
- Aufspalten von Collections
- Umwandeln zwischen verschiedenen Collection-Arten
- …
Man darf die Streams für Collections nicht mit den gleichnamigen Klassen für die Eingabe- und Ausgabe verwechseln. Es gibt zwar einige Gemeinsamkeiten aber trotzdem handelt es sich um grundverschiedene Konzepte, die weitgehend nur den Namen teilen.
Streams sind eng mit Lambdas verknüpft, da diese erst die Möglichkeit bieten, einfach eine Funktion an eine Methode des Streams zu übergeben.
Beispiel für Iteration mit Stream
List<String> namen = Arrays.asList("Franz", "Peter", "Jakob", "Hans");
namen.stream().forEach(name -> System.out.println(name));
List<String> namen = Arrays.asList("Franz", "Peter", "Jakob", "Hans");
namen.stream().forEach(System.out::println);
Das obige Beispiel zeigt die einfachste Form einer Stream-Operation, eine interne Iteration, bei der auf jedem Element die übergebene Funktion angewandt wird.
Theoretisch hätte man Streams in Java bereits früher, ohne Lambdas einführen und stattdessen innere Klassen verwenden können. Da hierdurch die Syntax aber extrem unübersichtlich wird, sind Streams nur zusammen mit Lambdas wirklich nutzbar. Ohne Lambdas müsste man das Beispiel wie folgt schreiben:
List<String> namen = Arrays.asList("Franz", "Peter", "Jakob", "Hans");
namen.stream().forEach(new Consumer<String>() {
public void accept(String name) {
System.out.println(name);
}
});
Es ist offensichtlich, dass hier der Gewinn einer internen Iteration durch die verworrene Syntax mehr als wettgemacht wird. Obwohl das Beispiel also semantisch vollkommen identisch mit dem vorhergehenden ist, ist es zu unübersichtlich, um nützlich zu sein.