Stream API Basics
A stream describes data processing
A Stream is not a new collection or a container that stores elements.
It is an object that describes a sequence of computations over a data source.
The source can be a collection, an array, or a specially created stream, for example.
List<String> names = List.of("Ann", "Bob", "Alexander");
List<String> longNames = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.toList();
In this chain, names remains a regular list.
stream() creates a stream over its elements,
then the processing steps are described, and toList() produces the final result.
The source collection itself is not changed.
Pipeline: source, intermediate steps, terminal
A typical stream pipeline consists of three parts:
- source — where the elements come from;
- intermediate operations — how the elements are filtered or transformed;
- terminal operation — what result should be produced.
Intermediate operations return a new stream and do not start the computation by themselves. Work begins when a terminal operation appears. Because of this, Java can process elements as needed instead of creating a separate temporary collection after every step.
Streams are lazy
Stream<String> stream = names.stream()
.filter(name -> {
System.out.println("check: " + name);
return name.length() > 3;
});
System.out.println("pipeline created");
long count = stream.count();
The check: ... messages start appearing only during count().
Before the terminal operation, the pipeline is only described.
We will separately examine which operations are intermediate and which are terminal on the next pages.
A stream is used once
After a terminal operation, the stream is considered consumed.
You cannot keep it in a variable, call count(),
and then try to traverse the same stream again.
If the processing must be repeated, create a new stream from the source.