Functional Programming & Streams

The Stream Pipeline

A stream is a lazily-evaluated pipeline over data: source → intermediate operations → one terminal operation. Nothing computes until the terminal runs; streams describe what, loops describe how.
  • Anatomy: source (stream(), Stream.of, Files.lines…) → intermediate ops → terminal op
  • Intermediate ops are lazy; the terminal op triggers a single fused pass
  • A stream never modifies its source; it is single-use — consume once
  • Prefer streams where they clarify; loops where they don't (EJ 45)
  • Streams of unbounded sources work because of laziness + short-circuit ops (limit, findFirst)
Declarative vs imperative
// How many long words?
long count = words.stream()
        .filter(w -> w.length() > 12)
        .count();

// Same, imperative:
int c = 0;
for (String w : words)
    if (w.length() > 12) c++;

Laziness is the engine: filter and map record intentions; count() runs the fused pipeline in one pass over the data, applying all stages per element. That is why Stream.iterate(0, n -> n + 1).map(...).limit(10) terminates — only 10 elements are ever pulled.

Creating streams
Stream.of("a", "b", "c");
Arrays.stream(intArray);
list.stream();
Stream.iterate(1L, n -> n * 2).limit(64);        // powers of two
Stream.generate(Math::random).limit(5);
try (Stream<String> lines = Files.lines(path)) {  // I/O-backed: close it!
    long n = lines.count();
}
Sources
  • Core Java, Volume II: Advanced Features (13th ed.)Ch. 1.1–1.2 — Stream Operations; Creation
  • Effective Java (3rd ed.)Items 45, 46
  • Learning Java (6th ed.)Ch. 7 — Streams