streams
intermediateStream Pipelines
Declarative pipelines for processing data — filter, map, reduce — that read like a description of what you want, not how to loop.
A stream is a way of describing what you want done to a collection, rather than writing the loop yourself.
A factory assembly line — raw items in, each station transforms or rejects them, packaging at the end. Nothing moves until the packaging line pulls.
Key Concepts
1
list.stream().filter(u -> u.isActive()).map(User::getName).toList();2
That reads as: take the list, keep the active ones, take their names, collect them.
3
Nothing happens until the last step. filter and map do not do any work when you call them. They
build up a description. Only the final operation — toList, forEach, count — actually runs anything.
filtermaptoListforEachcount
4
A pipeline with no final operation does nothing at all, and nothing warns you.
5
Items flow through one at a time. This is the part people get wrong. It does not filter the whole
list, then map the whole result.
6
Each item goes through every step before the next item starts. You can see it:
7
Stream.of("a","b","c")
.filter(s -> { print("filter " + s); return !s.equals("b"); })
.map(s -> { print("map " + s); return s.toUpperCase(); })
.findFirst();8
prints: filter a
map a <- stops here; b and c are never touched9
That is why findFirst is fast on a huge list: the stream stops pulling items as soon as it has an answer.
findFirst
10
Some steps have to see everything first. sorted cannot output anything until it has every item,
because the first item alphabetically might be the last one you give it. distinct has to remember
everything it has seen.
sorteddistinct
11
This makes the order of your steps matter:
12
list.stream().sorted(cmp).limit(10) // sorts a million items
list.stream().limit(10).sorted(cmp) // sorts ten13
Both are valid; they answer different questions. The first gives the ten smallest overall. The second
sorts the first ten it happens to find. Know which one you meant.
14
A stream can only be used once. After the final operation it is spent. Using it again throws
IllegalStateException. If you need two results, either collect once and reuse the collection, or build
the stream twice.
IllegalStateException
15
Do not change things from inside a stream. Adding to a list from forEach works when running normally
and breaks when the stream runs in parallel. Use collect to build a result instead.
forEachcollect
16
peek is for debugging only. It looks like a way to log each item, and Java does not promise to run it
for every item — it may skip it when it can prove the result does not depend on it. Logging with peek is
therefore unreliable.
peek
17
What the interviewer is probing.1. "When does a stream run?" Probing: laziness. Stalls: "When you call it." Moves up: only at
the final operation; without one, nothing happens.
18
2. "Filter then map, or map then filter?" Probing: whether you know items flow one at a time.
Stalls: "Same result." Moves up: same result, less work — filtering first means map runs on fewer
items.
map
19
3. "Why is sorted().limit(10) more expensive?" Probing: steps that buffer. Stalls: "They are the
same." Moves up: sorted must see everything before emitting anything.
sorted().limit(10)sorted
20
4. "Can you reuse a stream?" Probing: the one-shot rule. Stalls: "Yes." Moves up: no, it throws;
keep the collection instead.
21
5. "What is peek for?" Probing: the trap. Stalls: "Logging in production." Moves up:
debugging only, and not guaranteed to run for every item.
peek
22
Operation classes23
Intermediate, stateless filter, map, flatMap, peek, mapToInt
-> element passes straight through
Intermediate, stateful sorted, distinct, limit, skip
-> must buffer, sometimes the whole
stream
Terminal, short-circuit findFirst, findAny, anyMatch, allMatch,
noneMatch, limit
-> can stop the source early
Terminal, consuming collect, reduce, forEach, count, sum,
toArray24
Element-at-a-time, demonstrated25
Stream.of("a","b","c")
.filter(s -> { print("filter " + s); return !s.equals("b"); })
.map(s -> { print("map " + s); return s.toUpperCase(); })
.findFirst();26
prints: filter a
map a <- stops here; b and c never touched27
Ordering matters28
// sorts 1,000,000 elements, keeps 10
list.stream().sorted(cmp).limit(10)29
// sorts 10 -- but only equivalent if you want the first 10
// in encounter order, not the smallest 10
list.stream().limit(10).sorted(cmp)30
// for "smallest 10 overall", sorted().limit() is correct and
// the cost is inherent -- say so rather than misapplying the
// optimisation.31
Boxing
IntStream.range(0,1_000_000).sum() no allocation
Stream.iterate(0,i->i+1).limit(1_000_000)
.mapToInt(Integer::intValue).sum() 1M Integer objects