Stream API: filter, map, collect
So far, each station has worked on its own: removeIf removed, forEach printed, sort sorted. Now let's assemble the whole belt.
Boxes arrive from the warehouse. At the first station the unwanted ones are taken off the belt, and at the second each remaining box is repacked in new wrapping. At the end, a collector puts everything that made it into a crate. The warehouse does not change: the belt works with the boxes on it, not with the warehouse shelves. In Java, such a belt is built from a stream:
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("cat", "zebra", "ox", "horse");
List<String> result = words.stream()
.filter(w -> w.length() > 2)
.map(w -> w.toUpperCase())
.toList();
System.out.println(result); // [CAT, ZEBRA, HORSE]
System.out.println(words); // [cat, zebra, ox, horse]
}
}Let's go through the program step by step:
words.stream()puts the list's words on the belt: it creates a stream.filter(w -> w.length() > 2)is a station that lets through only words longer than two letters. The wordoxis taken off the belt.map(w -> w.toUpperCase())is a station that replaces each word with a new one written in capital letters.toList()is the collector at the end: it puts everything that made it into a new list.- The first print gives
[CAT, ZEBRA, HORSE], and the second shows thatwordshas not changed.
What a stream is
A stream is a belt that the elements of a source, such as a list, ride along. A stream does not store elements and does not change its source: filter does not remove ox from words, it only stops it from going further down the belt.
You already know the stations' cards. filter expects a Predicate and lets an element through if the Predicate returned true for it. map expects a Function and puts its result on the belt in place of the element.
This is where the belt picture is inaccurate. At a real station the worker repacks the same box. map, though, puts a new object on the belt, and the original string stays as it was.
Intermediate and terminal operations
Stream methods come in two kinds, and interviewers often ask you to tell them apart.
Intermediate
An intermediate operation is a station on the belt. It returns a new stream, so another one can follow it. filter and map are intermediate operations.
Terminal
A terminal operation is the collector at the end of the belt. It returns not a stream but a result: a list, a number or nothing. The chain cannot go on after it, so a terminal operation comes last. toList() is a terminal operation.
The toList() method appeared in Java 16. Older code writes collect(Collectors.toList()) instead: the collect method collects the elements, and Collectors.toList() tells it what into. You will often meet this form in real projects. A list from toList() cannot be changed, the same as a list from List.of: its add throws UnsupportedOperationException.
A result as one number
Sometimes the end of the belt needs not a list but a single number:
long n = words.stream().filter(w -> w.length() > 3).count();
System.out.println(n); // 2
int letters = words.stream().mapToInt(String::length).sum();
System.out.println(letters); // 15Line by line:
count()is a terminal operation: it counts how many elements reached the end.zebraandhorsemade it, so the result is2. The result's type islong.mapToInt(String::length)replaces each word with anint, its length. That gives a special stream of numbers, anIntStream.sum()is a terminal operation ofIntStream: it adds3 + 5 + 2 + 5and gives15.
Why mapToInt and not map? An ordinary stream has no sum method: it does not know that its elements are numbers. An IntStream does know, so it has sum.
Common mistake
The result of count() was put into an int variable:
int n = words.stream().filter(w -> w.length() > 3).count(); // ✗error: incompatible types: possible lossy conversion from long to intThe program does not compile: count() returns a long, which may not fit in an int, and the compiler will not narrow the type on its own. You have already seen a similar message in the operators theme, only there it was double. Declare the variable as long n.
Key points
- A stream is a belt over the elements of a source:
stream()puts them on the belt, and the source itself does not change. - Intermediate operations (
filter,map) return a new stream, terminal ones (toList,count,sum) return a result. filterlets through the elements for which thePredicatereturnedtrue, andmapreplaces each element with the result of theFunction.count()returns along, andmapToInt(...).sum()adds numbers up.