Java 8 Streams: Deklarative Datenverarbeitung – filter, map, reduce, collect & parallel
Was ist ein Stream?
Ein Stream ist eine Sequenz von Elementen, die aggregate Operationen (filter, map, reduce, …) unterstützt. Anders als Collections:
- Kein Speicher – Elemente werden on-demand berechnet.
- Funktional – Operationen verändern Quelle nicht, geben neuen Stream zurück.
- Pipeline-fähig – Intermediate-Ops (
filter, map) verketten, Terminal-Op (collect, forEach) schließt ab. - Automatische Iteration – keine expliziten Schleifen.
- Parallelisierbar –
parallelStream()nutzt Multicore-CPU.
Stream erzeugen
// Aus Collection
list.stream() // sequential
list.parallelStream() // parallel
// Aus Array
Arrays.stream(array)
Stream.of("a", "b", "c")
// Unendlich
Stream.iterate(0, n -> n + 2) // 0,2,4,6...
Stream.generate(() -> Math.random())
Wichtige Intermediate Operations
| Operation | Beschreibung | Beispiel |
|---|---|---|
filter(Predicate) |
Behält Elemente, die Prädikat erfüllen | .filter(s -> !s.isEmpty()) |
map(Function) |
Transformiert jedes Element | .map(String::toUpperCase) |
flatMap(Function) |
Transformiert zu Stream & flacht auf | .flatMap(s -> Stream.of(s.split(" "))) |
distinct() |
Entfernt Duplikate (equals) | |
sorted() / sorted(Comparator) |
Sortiert Stream | .sorted(Comparator.comparing(String::length)) |
limit(n) |
Begrenzt auf erste n Elemente | .limit(10) |
skip(n) |
Überspringt erste n Elemente | .skip(5) |
peek(Consumer) |
Side-Effect pro Element (Debugging) | .peek(System.out::println) |
Terminal Operations
| Operation | Rückgabe | Beschreibung |
|---|---|---|
forEach(Consumer) |
void |
Führt Aktion für jedes Element aus. |
collect(Collector) |
R |
Sammelt in Collection, String, Map, … |
reduce(BinaryOperator) |
Optional<T> |
Reduziert auf einzelnen Wert. |
reduce(identity, BinaryOperator) |
T |
Reduziert mit Startwert. |
count() |
long |
Anzahl Elemente. |
anyMatch/allMatch/noneMatch(Predicate) |
boolean |
Kurzschluss-Prüfung. |
findFirst/findAny() |
Optional<T> |
Findet Element. |
min/max(Comparator) |
Optional<T> |
Minimum/Maximum. |
Collectors – Ergebnisse sammeln
// To List/Set
list.stream().collect(Collectors.toList());
list.stream().collect(Collectors.toSet());
// To Map
stream.collect(Collectors.toMap(
User::getId, User::getName, (u1,u2)->u1, LinkedHashMap::new));
// Joining Strings
stream.map(Object::toString).collect(Collectors.joining(", "));
// Grouping / Partitioning
stream.collect(Collectors.groupingBy(User::getDepartment));
stream.collect(Collectors.partitioningBy(u -> u.isActive()));
// Statistics
IntSummaryStatistics stats = intStream.summaryStatistics();
// stats.getMax(), getMin(), getSum(), getAverage(), getCount()
Parallel Processing
// Einfach umschalten
long count = list.parallelStream()
.filter(s -> s.length() > 3)
.count();
// Oder: sequential() / parallel() in Pipeline
list.stream().parallel().filter(...).sequential().map(...).collect(...)
Vorsicht: Parallel nur bei CPU-intensiven, nebenwirkungsfreien Operationen & großer Datenmenge. Overhead bei kleinen Streams!
Vollständiges Beispiel: Java 7 vs. Java 8
import java.util.*;
import java.util.stream.*;
import java.util.IntSummaryStatistics;
public class StreamDemo {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
// --- Java 7 Style (Imperativ) ---
long emptyCount = 0;
for (String s : strings) if (s.isEmpty()) emptyCount++;
System.out.println("Leer (Java 7): " + emptyCount);
List<String> filtered = new ArrayList<>();
for (String s : strings) if (!s.isEmpty()) filtered.add(s);
System.out.println("Gefiltert (Java 7): " + filtered);
// --- Java 8 Style (Deklarativ) ---
emptyCount = strings.stream().filter(String::isEmpty).count();
System.out.println("Leer (Java 8): " + emptyCount);
filtered = strings.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
System.out.println("Gefiltert (Java 8): " + filtered);
// Quadrate einzig
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.distinct()
.collect(Collectors.toList());
System.out.println("Quadrate: " + squares); // [9, 4, 49, 25]
// Statistiken
IntSummaryStatistics stats = integers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.printf("Max: %d, Min: %d, Sum: %d, Avg: %.2f%n",
stats.getMax(), stats.getMin(), stats.getSum(), stats.getAverage());
// Zufallszahlen sortiert
new Random().ints().limit(10).sorted().forEach(System.out::println);
}
}
Fazit
Streams verschieben den Fokus von „wie“ (Schleifen, Indizes, temporäre Variablen) zu „was“ (Transformationen, Filter, Aggregationen). Das Ergebnis: kürzerer, lesbarer, wartbarer und leichter parallelisierbarer Code. Meistern Sie die Core-Operations (filter, map, flatMap, collect, reduce) – sie decken 90 % aller Use Cases ab.
Java
- Java Operatoren meistern: Typen, Syntax und praktische Beispiele
- Java Kommentare: Typen, Verwendung und Best Practices
- Java Enhanced For-Schleife meistern: Syntax, Beispiele und Best Practices
- Java Strings meistern: Erstellung, Methoden und Best Practices
- Java-Schnittstellen meistern: Konzepte, Implementierung und Best Practices
- Java anonyme innere Klassen meistern: Definition, Syntax und praktische Beispiele
- Java Try-With-Resources meistern: Automatisches Ressourcenmanagement erklärt
- Java Annotations erklärt: Typen, Platzierung und praktische Beispiele
- Java Assertions: Praktischer Leitfaden zur Fehlererkennung und Code-Reliabilität
- Zufallszahlen in Java generieren: Praktischer Leitfaden mit Random und Math.random