Appearance
Lesson 02 · Streams Basics
Objectives
After this lesson you will be able to:
- Create streams and tell intermediate from terminal operations.
- Explain laziness and why a stream runs only on a terminal operation.
- Use the core operations:
filter,map,sorted,distinct,limit,forEach,count.
Creating streams
java
Stream<String> s1 = Stream.of("a", "b", "c");
Stream<Integer> s2 = List.of(1, 2, 3).stream();
IntStream s3 = IntStream.range(0, 5); // 0,1,2,3,4
Stream<Double> s4 = Stream.generate(Math::random).limit(3); // infinite + limit
Stream<Integer> s5 = Stream.iterate(1, n -> n * 2).limit(4); // 1,2,4,8Intermediate vs terminal
A pipeline = a source → zero or more intermediate operations (return a Stream, lazy) → one terminal operation (produces a result/side-effect, eager, triggers execution).
| Intermediate (lazy) | Terminal (eager) |
|---|---|
filter, map, sorted, distinct, limit, peek, flatMap | forEach, count, collect, reduce, toList, findFirst, anyMatch, min/max |
java
long n = Stream.of("apple", "banana", "avocado")
.filter(s -> s.startsWith("a")) // intermediate
.map(String::toUpperCase) // intermediate
.count(); // terminal → 2Exam trap
Nothing runs until the terminal operation. A pipeline with only intermediate operations does nothing. And a stream is single-use — operating on it twice throws IllegalStateException: stream has already been operated upon or closed.
Laziness and short-circuiting
Intermediate operations are fused and run per element, so short-circuiting terminals (findFirst, anyMatch, limit) can stop early — essential for infinite streams.
java
Optional<Integer> first = Stream.iterate(1, n -> n + 1) // infinite
.filter(n -> n % 7 == 0)
.findFirst(); // stops at 7Gotcha
peek is intermediate and meant for debugging; if no terminal runs, its action never fires. Don't rely on peek for real work, and avoid stateful lambdas in map/filter — order and parallelism make them unreliable.
Key Takeaways
- Build streams with
Stream.of,collection.stream(),IntStream.range,Stream.generate/iterate(+limitfor infinite ones). - Intermediate ops are lazy and return a stream; the single terminal op is eager and triggers the whole pipeline. No terminal = nothing happens.
- A stream is single-use — reusing it throws
IllegalStateException. - Short-circuiting terminals (
findFirst/anyMatch/limit) let infinite streams terminate.
Lesson Quiz
What does this code do?
Stream.of("a","b").filter(s -> s.equals("a")).map(String::toUpperCase);Which is a TERMINAL operation?
What happens if you operate on the same stream twice?
Stream<Integer> s = Stream.of(1,2,3); s.count(); s.count();
Why does this terminate despite an infinite source?
Stream.iterate(1, n -> n+1).filter(n -> n%7==0).findFirst();
Next: Reduction & Collectors. Run the matching code in labs/src/main/java/com/jse21/m06_streams/.