liveBook · Manning

Streaming

As explained in 100 Java Mistakes and How to Avoid Them

The Java Stream API provides pipelines for processing sequences of elements through intermediate and terminal operations.

The Java Stream API processes element sequences as a fluent pipeline: create a stream, compose intermediate operations such as map() and filter(), then trigger work with one terminal operation such as count(), toArray(), or collect(). Intermediate operations are lazy recipes, not execution steps; nothing is processed until the terminal operation runs. section 9.2

Treat pipeline functions as side-effect-free. An implementation may omit work whose result is unnecessary, and operations may run in a different order or concurrently for parallel streams. Mutating an external collection from filter(), map(), or peek() can therefore leave incomplete data, change results, or cause concurrent-update failures. section 9.2 section 9.13

Map<Boolean, List<String>> partitions = list.stream()
  .collect(Collectors.partitioningBy(str -> str.contains("needle")));
List<String> containNoNeedle = partitions.get(false);
List<String> containNeedle = partitions.get(true);

Use partitioningBy() when one predicate should split input into matching and nonmatching lists, rather than mutating one list as a filter() side effect. section 9.2

A Stream is single-use. Storing one in a variable makes accidental reuse easier: an intermediate operation commonly returns a new stream and consumes the old one, while any terminal operation consumes the pipeline. Reassign a conditionally transformed stream, express the condition in the chain, or collect elements when multiple passes are genuinely needed. section 9.3 section 7.2

Conditional skipping

Calling stream.skip(1) without saving its returned stream can make later use of stream fail when the header is skipped. Either reassign it with stream = stream.skip(1), or keep one pipeline with .skip(skipHeader ? 1 : 0) before map(String::trim). section 9.3

Honor operation contracts even when sequential execution appears to work. In particular, reduce(identity, accumulator) requires an identity-preserving, associative accumulator. A reduction used to reproduce List hash-code calculation can yield the expected sequential result despite violating those rules, then produce a wrong result after switching to parallelStream(). section 9.5

Use streams to remove manual index handling, but weigh their costs:

  • Map array elements directly when index-based loops only transform each element; stream internals then encapsulate the indices. section 3.5
  • Use IntStream.rangeClosed(lo, hi) for inclusive numeric ranges when it avoids a loop-counter overflow corner case. section 3.7
  • Avoid assuming every terminal operation accepts null values; verify the operation when nulls must reach it. section 9.13

How 100 Java Mistakes and How to Avoid Them covers it

How deep it goes
Explains lazy execution, side-effect risks, single consumption, and reduce contracts, including sequential-versus-parallel failure modes.
Examples
Demonstrates predicate partitioning, conditional skip, repeated consumption, hash-code reduction, and inclusive numeric ranges.
What you can do after
Apply it via Java Stream API pipelines while avoiding side effects, reuse, incompatible reductions, and unsupported null handling.
How it connects
Connects streams to lambdas, collectors, parallel streams, reduce, arrays, loops, and List equality.

Also explained in

Compare how 19 books explain streaming →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Java Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.