Notifications

No notifications

/Phase 4

Streams API — Functional Pipelines

A Stream is a lazy pipeline of operations on a sequence of elements. You build it from a source (collection, array, generator), chain *intermediate* operations (map, filter, sorted), and finish with a *terminal* operation (collect, reduce, forEach). Streams replace half the for-loops you used to write — and read more like a description of *what* you want, not *how* to compute it.

On this page

Detailed Theory

# Streams API

Pipeline anatomy

SOURCE  →  intermediate ops (lazy)  →  terminal op (eager)
Nothing happens until the terminal op runs.

List<String> names = List.of("alice", "bob", "carol", "dan");

List<String> result = names.stream() // SOURCE .filter(n -> n.length() > 3) // intermediate .map(String::toUpperCase) // intermediate .sorted() // intermediate .toList(); // TERMINAL (Java 16+)

System.out.println(result); // [ALICE, CAROL]

Common sources

list.stream();
Arrays.stream(arr);
Stream.of("a", "b", "c");
Stream.iterate(1, x -> x * 2).limit(5);     // 1, 2, 4, 8, 16
Stream.generate(Math::random).limit(3);
IntStream.range(0, 10);                      // 0..9
IntStream.rangeClosed(1, 10);                // 1..10

Common intermediate operations

opdoes
filter(pred)keep elements matching pred
map(fn)transform each element
flatMap(fn)map each → stream, then flatten
sorted() / sorted(cmp)sort
distinct()drop duplicates
limit(n)first n
skip(n)drop first n
peek(c)side-effect (debug only)

Common terminal operations

opreturns
toList()List (Java 16+, immutable)
collect(...)anything via a Collector
forEach(c)void
count()long
min(cmp) / max(cmp)Optional
findFirst() / findAny()Optional
anyMatch / allMatch / noneMatchboolean
reduce(...)aggregate (sum, product, …)

Collectors

import static java.util.stream.Collectors.*;

people.stream().collect(toList()); people.stream().collect(toSet()); people.stream().collect(toMap(Person::id, p -> p)); people.stream().collect(groupingBy(Person::department)); people.stream().collect(groupingBy(Person::department, counting())); people.stream().collect(partitioningBy(p -> p.salary() > 50000)); words.stream().collect(joining(", ", "[", "]")); // [a, b, c]

reduce

int sum = nums.stream().reduce(0, Integer::sum);
String concat = words.stream().reduce("", String::concat);
Optional<Integer> max = nums.stream().reduce(Integer::max);

Primitive streams (avoid boxing)

IntStream, LongStream, DoubleStream add sum, average, min, max, mapToObj.
int total = IntStream.rangeClosed(1, 100).sum();        // 5050
double avg = nums.stream().mapToInt(Integer::intValue).average().orElse(0);

Parallel streams (use sparingly)

long count = bigList.parallelStream().filter(...).count();
Only worth it for CPU-heavy ops on large data; the work is split via the *common ForkJoinPool*.

Gotchas

  • Streams are single-use — once consumed, you can't iterate again.
  • peek is for debugging only — don't mutate state through it.
  • forEach doesn't guarantee order on parallel streams; use forEachOrdered if you need it.