Java Streams API for collection processing
Stream creation, filter, map, reduce, collect, sorted, distinct, findFirst, method references, lazy evaluation, terminal vs intermediate operations
Streams API
Streams provide a declarative way to process collections as data pipelines. Operations chain together and execute lazily — nothing runs until a terminal operation is called.
Basic Pipeline
import java.util.List;
import java.util.stream.Collectors;
List names = List.of("Alice", "Bob", "Anna", "Charlie", "Amy");
List result = names.stream()
.filter(n -> n.startsWith("A")) // intermediate — lazy
.map(String::toUpperCase) // intermediate — lazy
.sorted() // intermediate — lazy
.collect(Collectors.toList()); // terminal — triggers execution
System.out.println(result); // [ALICE, AMY, ANNA]
Numeric Reductions
List nums = List.of(3, 1, 4, 1, 5, 9, 2, 6);
int sum = nums.stream().mapToInt(Integer::intValue).sum();
long cnt = nums.stream().filter(n -> n > 3).count();
Optional max = nums.stream().max(Integer::compareTo);
Grouping with Collectors
Map> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// {5=[Alice, Anna], 3=[Bob, Amy], 7=[Charlie]}
Streams do not modify the source collection — they produce new results. Each pipeline is single-use; calling a terminal operation closes the stream. Attempting to reuse a consumed stream throws IllegalStateException.
For parallel processing, replace stream() with parallelStream(). Parallel streams split work across the common ForkJoinPool — beneficial for large CPU-bound pipelines, but add overhead for small collections.
Streams are not a silver bullet — for simple iterations with no filtering or mapping, a traditional for-each loop is faster and clearer. Use streams when the pipeline logic is complex enough that the declarative style genuinely improves readability and reduces errors.
