Skip to content
Theory · Lambdas and the Stream API · lesson 7 of 12

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:

JAVA
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:

  1. words.stream() puts the list's words on the belt: it creates a stream.
  2. filter(w -> w.length() > 2) is a station that lets through only words longer than two letters. The word ox is taken off the belt.
  3. map(w -> w.toUpperCase()) is a station that replaces each word with a new one written in capital letters.
  4. toList() is the collector at the end: it puts everything that made it into a new list.
  5. The first print gives [CAT, ZEBRA, HORSE], and the second shows that words has 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:

JAVA
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); // 15

Line by line:

  1. count() is a terminal operation: it counts how many elements reached the end. zebra and horse made it, so the result is 2. The result's type is long.
  2. mapToInt(String::length) replaces each word with an int, its length. That gives a special stream of numbers, an IntStream.
  3. sum() is a terminal operation of IntStream: it adds 3 + 5 + 2 + 5 and gives 15.

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:

JAVA
int n = words.stream().filter(w -> w.length() > 3).count(); // ✗
TEXT
error: incompatible types: possible lossy conversion from long to int

The 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.
  • filter lets through the elements for which the Predicate returned true, and map replaces each element with the result of the Function.
  • count() returns a long, and mapToInt(...).sum() adds numbers up.