Streams
Prerequisites: Lambda expressions & functional interfaces
A Stream describes a pipeline of operations over a collection —
filter, transform, and collect — without writing an explicit loop.
List<String> names = List.of("Ada", "Bob", "Grace", "Al");
List<String> longNames = names.stream()
.filter(name -> name.length() > 2)
.map(String::toUpperCase)
.sorted()
.toList();
System.out.println(longNames); // [ADA, GRACE]
A stream is lazy — nothing runs until a terminal operation like
.toList(), .forEach(), or .count() is called — and it can only
be consumed once.
Common mistakes
- Reusing a
Streamvariable after a terminal operation already ran on it — throwsIllegalStateException. Build a fresh stream (e.g. call.stream()again) each time. - Reaching for a stream pipeline when a plain for-each loop would be clearer — streams shine for filter/transform/collect chains, not every loop.
Practice
Given List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);, use a stream
to produce a list of only the even numbers, doubled.
Show solution
List<Integer> result = nums.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.toList();
// [4, 8, 12]