Java 53: Stream Pipelines — map, filter & collect
easy⏱ 5 mincoursejava
Intermediate vs terminal operations
Intermediate ops (map, filter, flatMap, sorted, distinct, limit) are lazy — they return a new Stream and add a stage to the pipeline but execute nothing. Terminal ops (collect, reduce, count, forEach, findFirst) are eager — they pull elements through every stage and produce a result or side effect. A stream can be consumed once: after a terminal op it's closed.
List<String> names = List.of("ada", "linus", "grace", "ken");
List<String> result = names.stream()
.filter(n -> n.length() > 3) // intermediate (lazy)
.map(String::toUpperCase) // intermediate (lazy)
.sorted() // intermediate (lazy)
.collect(Collectors.toList()); // terminal -> runs the pipeline
// [ADA? no — len 3 dropped] -> [GRACE, LINUS]
Element-at-a-time, not stage-at-a-time
Streams don't materialize an intermediate list between each operation. Instead each element is pushed through the whole pipeline before the next element starts. So filter then map on [1,2,3,4] runs filter(1) → (dropped), filter(2) → map(2), … — this fusion is why streams stay memory-light and why short-circuit ops like findFirst can stop early.
Build a lazy stream that proves laziness
Implement LazyStream<T> with map, filter (each records a stage and returns a new stream — running nothing) and a terminal collect() that finally pushes every source element through all stages. Track a mapCalls counter. Pipeline [1,2,3,4,5,6] through filter(even) then map(x*10) and collect. Print the result [20,40,60] and confirm map ran only 3 times (once per surviving element), not 6.
Collectors are the real power
collect(Collectors.toList()) is just the start. groupingBy, partitioningBy, toMap, joining, counting, and summingInt turn a stream into maps, partitions, and aggregates in one terminal call. Reaching for a manual for loop to build a Map is usually a sign a Collector would read better.
Quiz: does filter run before the terminal op?
If you build stream.filter(...).map(...) but never call a terminal op, how many times does filter run? Zero — intermediate ops are lazy and only execute when a terminal op pulls elements. A pipeline with no terminal op does no work at all; it's just a description.