Unravel › Java concepts › Collections and streams
Stream operations in depth
Intermediate steps (filter, map, flatMap, sorted, distinct, limit) shape the data; terminal steps (reduce, collect, count, anyMatch, findFirst) produce the answer.
Learn these first
At a glance
A factory line with stations: each intermediate step is a station that does one thing, and nothing moves until the last station (the terminal step) asks for the finished product.
- 1map changes each element, flatMap turns each element into a stream and flattens them (list of orders to a stream of lines)filter keeps matches, distinct removes duplicates, sorted orders, limit/skip page through.
- 2reduce(0, Integer::sum) folds all elements into one value; the identity and the function must be associative for parallel usemapToInt(...).sum() is simpler and faster.
- 3IntStream.rangeClosed(1, 5) replaces counting loops, .boxed() converts to Stream<Integer>, .summaryStatistics() gives count, min, max, average and sum together
- 4anyMatch, allMatch and noneMatch short-circuit; findFirst returns an Optional; takeWhile/dropWhile work on sorted data
- 5Stream.iterate(1, x -> x * 2).limit(10) and Stream.generate(supplier) create infinite streams, so always bound them
- Lambdas in a pipeline should be pure: no changing outside variables
- sorted and distinct are stateful: they must see all elements first, so they destroy laziness for whatever comes after them in a very long or infinite stream
- The pipeline is a chain of Sink objects; the terminal operation pulls elements from the spliterator of the source and pushes each through the sinks one by one
import java.util.*;import java.util.stream.*;public class Numbers { static int sumOfSquares(int n) { return IntStream.rangeClosed(1, n).map(i -> i * i).sum(); } static List<String> words(List<String> lines) { return lines.stream().flatMap(l -> Arrays.stream(l.split(" "))).distinct().sorted().collect(Collectors.toList()); } static boolean anyNegative(int[] a) { return Arrays.stream(a).anyMatch(x -> x < 0); } static int product(List<Integer> xs) { return xs.stream().reduce(1, (a, b) -> a * b); }}- line 4 uses rangeClosed, the core of Stream operations in depth
- line 6 uses distinct, the core of Stream operations in depth
- line 8 uses anyMatch, the core of Stream operations in depth
- line 9 uses reduce, the core of Stream operations in depth
Read the full explanation (about 1 min)
Intermediate steps (filter, map, flatMap, sorted, distinct, limit) shape the data; terminal steps (reduce, collect, count, anyMatch, findFirst) produce the answer. Primitive streams (IntStream) avoid boxing.
A mental model
A factory line with stations: each intermediate step is a station that does one thing, and nothing moves until the last station (the terminal step) asks for the finished product.
How it works
map changes each element, flatMap turns each element into a stream and flattens them (list of orders to a stream of lines). filter keeps matches, distinct removes duplicates, sorted orders, limit/skip page through.
reduce(0, Integer::sum) folds all elements into one value; the identity and the function must be associative for parallel use. mapToInt(...).sum() is simpler and faster.
IntStream.rangeClosed(1, 5) replaces counting loops, .boxed() converts to Stream<Integer>, .summaryStatistics() gives count, min, max, average and sum together.
anyMatch, allMatch and noneMatch short-circuit; findFirst returns an Optional; takeWhile/dropWhile work on sorted data.
Stream.iterate(1, x -> x * 2).limit(10) and Stream.generate(supplier) create infinite streams, so always bound them.
Going deeper
Lambdas in a pipeline should be pure: no changing outside variables. peek is for debugging only. A stream that mutates a list while reading it gives undefined behaviour.
sorted and distinct are stateful: they must see all elements first, so they destroy laziness for whatever comes after them in a very long or infinite stream.
Prefer a simple loop when the pipeline needs indexes, early return, checked exceptions or more than a few steps.
Under the hood
The pipeline is a chain of Sink objects; the terminal operation pulls elements from the spliterator of the source and pushes each through the sinks one by one. Short-circuiting operations set a flag so the source stops early.
Common mistakes
- Reusing a stream (
IllegalStateException). - Forgetting the terminal operation, so nothing runs.
- Using
reducewith a non-associative function. - Infinite stream without
limit.
How Unravel recognises it
When you paste code, Unravel looks for these signs of stream operations in depth. Spotting them yourself is a good habit when you read unfamiliar code:
- Method calls:
reduce()mapToInt()mapToObj()mapToLong()mapToDouble()boxed()summaryStatistics()takeWhile()dropWhile()peek()iterate()generate()rangeClosed()anyMatch()and 8 more
Part of a learning path
Related concepts
Frequently asked questions
What is Stream operations in depth?
Intermediate steps (filter, map, flatMap, sorted, distinct, limit) shape the data; terminal steps (reduce, collect, count, anyMatch, findFirst) produce the answer. Primitive streams (IntStream) avoid boxing.
What are the most common mistakes with Stream operations in depth?
Reusing a stream (IllegalStateException). Forgetting the terminal operation, so nothing runs. Using reduce with a non-associative function. Infinite stream without limit.
How can I see Stream operations in depth in real code?
Paste your own Java into Unravel and it will point out where stream operations in depth appears, explain how it works at the detail level you choose, and draw the flow of the code around it.