filter, map, reduce, collect — transform data without mutation.
Published February 5, 2025
Streams are a declarative way to process collections of data — filter, transform, reduce — without modifying the source.
List<String> names = List.of("Alice", "Bob", "Charlie", "Dave");
Stream<String> stream = names.stream();
Intermediate operations return a new stream — they are lazy (not evaluated until a terminal operation).
names.stream()
.filter(n -> n.length() > 3) // keep names longer than 3 chars
.map(String::toUpperCase) // transform to uppercase
.sorted() // sort alphabetically
.distinct() // remove duplicates
.limit(10) // take at most 10
.forEach(System.out::println);
Terminal operations trigger evaluation and return a non-stream result.
// Collect to list
List<String> result = names.stream()
.filter(n -> n.startsWith("A"))
.collect(Collectors.toList());
// Reduce to single value
int totalLength = names.stream()
.mapToInt(String::length)
.sum();
// Find first match
Optional<String> first = names.stream()
.filter(n -> n.contains("li"))
.findFirst();
// Count
long count = names.stream().filter(n -> n.length() > 3).count();
// Any/all/none match
bool any = names.stream().anyMatch(n -> n.startsWith("Z"));
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream) // flatten
.collect(Collectors.toList()); // [1, 2, 3, 4]
names.parallelStream() // distributes work across fork-join pool
.map(String::toUpperCase)
.collect(Collectors.toList());
⚠ Parallel streams add overhead. Only use them for CPU-intensive operations on large collections (> 10K elements).
Be able to explain the lazy evaluation of streams — intermediate operations are not executed until a terminal operation is called. This is why the pipeline is efficient: short-circuit operations like findFirst() or limit() stop processing as soon as the result is found.