filter and map

Streams · lesson 4 of 42 · 3 min read

Keep what you want with filter, reshape it with map, and chain the two safely.

Open this lesson in the learning hub

Key points

  • filter takes a Predicate and keeps whatever passes. The count can shrink; elements never change.
  • map takes a Function and swaps each element for something else. The count stays the same.
  • Method references such as Book::title read better than b -> b.title().
  • Filter first, then map. Transforming elements you are about to discard is wasted work.
  • Keep lambdas pure: no printing, no field updates, no touching the source collection.

Example

import java.util.*;

public class Main {
    record Book(String title, String genre, int pages) {}

    public static void main(String[] args) {
        List<Book> books = List.of(
                new Book("Dune", "scifi", 412),
                new Book("Neuromancer", "scifi", 271),
                new Book("Emma", "classic", 474));

        // filter keeps or drops. It never changes an element.
        List<Book> scifi = books.stream().filter(b -> b.genre().equals("scifi")).toList();

        // map transforms. Exactly one out for every one in.
        List<String> titles = books.stream().map(Book::title).toList();

        System.out.println("scifi count : " + scifi.size());
        System.out.println("titles      : " + titles);
        System.out.println("page counts : " + books.stream().map(Book::pages).toList());
        System.out.println("chained     : " + books.stream()
                .filter(b -> b.pages() > 300)
                .map(b -> b.title().toUpperCase())
                .toList());
    }
}

filter changes how many; map changes what.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.