Java Streams Cheat Sheet
Updated for Java 25 — includes Gatherers (JEP 485).
Stream erzeugen
// Aus Collection
list.stream()
list.parallelStream()
// Aus Werten
Stream.of("a", "b", "c")
Stream.empty()
// Aus Array
Arrays.stream(array)
Arrays.stream(array, startInclusive, endExclusive)
// Generieren
Stream.iterate(0, n -> n + 2) // 0, 2, 4, 6, ...
Stream.iterate(0, n -> n < 100, n -> n + 2) // mit Abbruchbedingung (Java 9+)
Stream.generate(Math::random) // unendlicher Stream
// Primitive Streams
IntStream.range(0, 10) // 0..9
IntStream.rangeClosed(1, 10) // 1..10
LongStream.of(1L, 2L, 3L)
// Aus String
"hello".chars() // IntStream
// Aus Datei
Files.lines(Path.of("file.txt"))
Intermediate Operations
.filter(x -> x > 5) // Filtern
.map(String::toUpperCase) // Transformieren
.mapToInt(String::length) // zu IntStream
.flatMap(list -> list.stream()) // 1:n Transformation, flatten
// mapMulti (Java 16+) — performanter als flatMap bei wenigen Elementen
.<String>mapMulti((element, consumer) -> {
if (element.startsWith("A")) {
consumer.accept(element.toUpperCase());
consumer.accept(element.toLowerCase());
}
})
.distinct() // Duplikate entfernen
.sorted() // natürliche Sortierung
.sorted(Comparator.reverseOrder())
.sorted(Comparator.comparing(Person::age))
.peek(System.out::println) // Debugging (nicht für Side-Effects!)
.limit(10) // Erste n Elemente
.skip(5) // Erste n überspringen
.takeWhile(x -> x < 100) // Nimmt solange Bedingung wahr (Java 9+)
.dropWhile(x -> x < 10) // Überspringt solange Bedingung wahr (Java 9+)
Terminal Operations
// Sammeln
.toList() // Unmodifiable List (Java 16+)
.toArray(String[]::new)
.collect(Collectors.toList()) // Mutable List
.collect(Collectors.toSet())
.collect(Collectors.toCollection(TreeSet::new))
// Reduzieren
.count()
.min(Comparator.naturalOrder()) // Optional<T>
.max(Comparator.naturalOrder()) // Optional<T>
.reduce(0, Integer::sum)
.reduce((a, b) -> a + b) // Optional<T>
// Suchen
.findFirst() // Optional<T>
.findAny() // Optional<T> (parallel-freundlich)
// Prüfen
.anyMatch(x -> x > 5) // boolean
.allMatch(x -> x > 0)
.noneMatch(x -> x < 0)
// Ausgeben
.forEach(System.out::println)
.forEachOrdered(System.out::println) // Reihenfolge garantiert
Collectors
// Strings zusammenfügen
Collectors.joining(", ")
Collectors.joining(", ", "[", "]") // mit Prefix/Suffix
// Gruppieren
Collectors.groupingBy(Person::city) // Map<City, List<Person>>
Collectors.groupingBy(Person::city, Collectors.counting()) // Map<City, Long>
Collectors.groupingBy(Person::city,
Collectors.mapping(Person::name, Collectors.toList())) // Map<City, List<String>>
// Partitionieren (boolean-Gruppen)
Collectors.partitioningBy(n -> n % 2 == 0) // Map<Boolean, List<T>>
// Statistiken
Collectors.summarizingInt(Person::age) // IntSummaryStatistics
// Zu Map
Collectors.toMap(Person::id, Person::name)
Collectors.toMap(Person::id, Person::name, (a, b) -> a) // Merge bei Duplikaten
Collectors.toUnmodifiableMap(Person::id, Person::name)
// Downstream-Collectors verschachteln
Collectors.groupingBy(Person::dept,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparing(Person::salary)),
Optional::get))
// Teeing — zwei Collectors parallel, Ergebnisse zusammenführen (Java 12+)
Collectors.teeing(
Collectors.minBy(Comparator.naturalOrder()),
Collectors.maxBy(Comparator.naturalOrder()),
(min, max) -> new Range(min.get(), max.get()))
Stream Gatherers (Java 24+, JEP 485)
Gatherers sind benutzerdefinierte Intermediate Operations — das Gegenstück zu Collectors.
// Built-in Gatherers
import java.util.stream.Gatherers;
// Feste Fenster: [1,2,3,4,5] → [[1,2],[3,4],[5]]
stream.gather(Gatherers.windowFixed(2))
// Gleitende Fenster: [1,2,3,4] → [[1,2],[2,3],[3,4]]
stream.gather(Gatherers.windowSliding(2))
// Fold — alle Elemente zu einem Ergebnis (wie reduce, aber mit Initialwert und anderem Typ)
stream.gather(Gatherers.fold(() -> "", (str, el) -> str + el))
// Scan — wie fold, gibt aber jeden Zwischenzustand aus
stream.gather(Gatherers.scan(() -> 0, Integer::sum))
// [1,2,3] → [1, 3, 6]
// mapConcurrent — parallele Verarbeitung mit Virtual Threads
stream.gather(Gatherers.mapConcurrent(10, this::fetchFromApi))
Eigenen Gatherer schreiben
// Beispiel: Nur Elemente durchlassen, die sich vom Vorgänger unterscheiden
Gatherer<String, ?, String> dedupConsecutive = Gatherer.ofSequential(
() -> new Object() { String last = null; }, // Initializer (State)
(state, element, downstream) -> { // Integrator
if (!element.equals(state.last)) {
state.last = element;
return downstream.push(element); // true = weitermachen
}
return true;
}
);
stream.gather(dedupConsecutive).toList();
// ["a","a","b","b","a"] → ["a","b","a"]
// Mit Finisher (wird am Ende aufgerufen)
Gatherer.ofSequential(initializer, integrator, finisher)
// Parallel-fähig (mit Combiner)
Gatherer.of(initializer, integrator, combiner, finisher)
Primitive Streams
IntStream / LongStream / DoubleStream
.sum()
.average() // OptionalDouble
.summaryStatistics()
// Boxing
intStream.boxed() // Stream<Integer>
intStream.asLongStream() // LongStream
intStream.asDoubleStream()
// Objekt-Stream → Primitiv
stream.mapToInt(String::length)
stream.flatMapToInt(...)
Tipps & Fallstricke
// Stream kann nur einmal konsumiert werden!
Stream<String> s = list.stream();
s.count();
s.count(); // IllegalStateException!
// toList() ist unmodifiable — kein add/remove möglich
var list = stream.toList(); // UnsupportedOperationException bei Mutation
// parallel() nicht blindlings verwenden
// Gut bei: CPU-intensiv, große Datenmengen, unabhängige Operationen
// Schlecht bei: I/O, kleine Datenmengen, gemeinsamer Zustand
// Reihenfolge bei parallel Streams
parallelStream.forEachOrdered(...) // erzwingt Reihenfolge
parallelStream.forEach(...) // beliebige Reihenfolge
// Optional richtig verwenden
stream.findFirst()
.map(String::toUpperCase)
.orElse("default");