Skip to content

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,8

Intermediate 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, flatMapforEach, 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 → 2

Exam 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 7

Gotcha

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 (+ limit for 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

Lesson Quiz · Streams Basics0 / 4
  1. What does this code do?

    Stream.of("a","b").filter(s -> s.equals("a")).map(String::toUpperCase);
    • APrints A
    • BReturns [A]
    • CNothing — no terminal operation
    • DThrows
  2. Which is a TERMINAL operation?

    • Afilter
    • Bmap
    • Csorted
    • Dcount
  3. What happens if you operate on the same stream twice?

    Stream<Integer> s = Stream.of(1,2,3);
    s.count();
    s.count();
    • AReturns 3 twice
    • BIllegalStateException
    • CReturns 0
    • DCompile error
  4. Why does this terminate despite an infinite source?

    Stream.iterate(1, n -> n+1).filter(n -> n%7==0).findFirst();
    • Aiterate is finite
    • BfindFirst short-circuits — stops at the first match
    • Cfilter caps it
    • DIt doesn't terminate

Next: Reduction & Collectors. Run the matching code in labs/src/main/java/com/jse21/m06_streams/.