Skip to content

Java Streams

A stream describes a pipeline over elements. It is not a collection and does not store its own elements. A pipeline contains a source, zero or more intermediate operations, and one terminal operation.

Laziness and single use

Intermediate operations such as map and filter are lazy; traversal begins when a terminal operation requests results. A stream cannot be reused after a terminal operation. The broader lazy-versus-eager guide covers evaluation, resource lifetime, snapshots, and persistence hazards.

record Reading(String sensor, double value) {}

List<String> activeSensors = readings.stream()
        .filter(reading -> reading.value() > 0.0)
        .map(Reading::sensor)
        .distinct()
        .sorted()
        .toList();

The source is not modified, but non-interference is a responsibility of the caller: mutating the source during traversal or mutating shared state from operations can make behavior unsafe or nondeterministic.

Operation categories

Kind Examples Result
Stateless intermediate map, filter Another stream
Stateful intermediate distinct, sorted, limit Another stream, possibly buffered
Short-circuiting findFirst, anyMatch, limit May stop early
Terminal reduction reduce, collect, count Non-stream result

peek is mainly an observation hook; do not make correctness depend on its side effects because optimizations and short-circuiting affect which elements are observed.

Reduction

Map<String, Double> maximumBySensor = readings.stream()
        .collect(Collectors.toMap(
                Reading::sensor,
                Reading::value,
                Math::max));

For parallel reduction, the identity and accumulator/combiner must satisfy the documented algebraic compatibility requirements. Associativity is essential; floating-point addition is not mathematically associative, so reassociation can change rounding.

Parallel streams

Parallelism is not a free speed switch. It works best for sufficiently large, CPU-bound, splittable workloads with stateless operations and cheap combining. Blocking I/O, shared mutation, encounter-order constraints, small inputs, and contention can make it slower or unsafe. Measure with a representative benchmark.

Exercises

  1. Replace a nested stream with flatMap.
  2. Explain the difference between findFirst and findAny in parallel.
  3. Write a collector that groups readings by sensor and computes summary statistics.