Stream Filter, Map and Reduce

Naveen AnamaneniUpdated · 5 min read

filter, map and reduce are the three most important stream operations. filter is used to keep only the elements that pass a test. map is used to transform each element into another value. reduce is used to combine all elements into a single result, such as a total. Almost every stream pipeline uses at least one of them. In this lesson, you will learn each operation with examples, and then use all three together.

Declaration

Declaration
Stream<T> filter(Predicate<? super T> predicate)
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
T reduce(T identity, BinaryOperator<T> accumulator)
Optional<T> reduce(BinaryOperator<T> accumulator)

filter: Keep Matching Elements

filter takes a Predicate and keeps each element for which it returns true.

FilterDemo.java▶ Try it
import java.util.List;
 
public class FilterDemo {
    public static void main(String[] args) {
        List<Integer> marks = List.of(91, 38, 85, 42, 77);
        System.out.println(marks.stream().filter(m -> m >= 50).toList());
 
        List<String> names =
            List.of("Navika", "Srihitha", "Advitha", "Anamaneni");
        System.out.println(names.stream()
            .filter(n -> n.startsWith("A"))
            .filter(n -> n.length() < 9)
            .toList());
    }
}
Output
[91, 85, 77]
[Advitha]

Explanation: The first filter keeps marks of 50 or more. Two filter calls in a row act like "and": the names must start with A and be shorter than 9 letters.

map: Transform Each Element

map takes a Function and replaces each element with the function's result. The type can change, for example from String to Integer.

MapDemo.java▶ Try it
import java.util.List;
 
public class MapDemo {
    public static void main(String[] args) {
        List<String> names = List.of("navika", "srihitha", "advitha");
 
        System.out.println(names.stream().map(String::toUpperCase).toList());
        System.out.println(names.stream().map(String::length).toList());
        System.out.println(names.stream()
            .map(n -> n.substring(0, 1).toUpperCase() + n.substring(1))
            .toList());
    }
}
Output
[NAVIKA, SRIHITHA, ADVITHA]
[6, 8, 7]
[Navika, Srihitha, Advitha]

Explanation: The first map changes each name to upper case. The second changes each String into an Integer (its length). The third capitalizes the first letter.

reduce: Combine into One Value

reduce combines elements two at a time. With an identity (a starting value), it returns a value; without one, it returns an Optional, because the stream might be empty.

ReduceDemo.java▶ Try it
import java.util.List;
import java.util.Optional;
 
public class ReduceDemo {
    public static void main(String[] args) {
        List<Integer> marks = List.of(91, 78, 85);
 
        int total = marks.stream().reduce(0, (a, b) -> a + b);
        System.out.println("Total: " + total);
 
        int product = marks.stream().reduce(1, (a, b) -> a * b);
        System.out.println("Product: " + product);
 
        Optional<Integer> max = marks.stream().reduce(Integer::max);
        System.out.println("Max: " + max.get());
 
        String joined = List.of("Navika", "Srihitha").stream()
            .reduce("", (a, b) -> a + b.charAt(0));
        System.out.println("Initials: " + joined);
    }
}
Output
Total: 254
Product: 603330
Max: 91
Initials: NS

Explanation: For the total, reduce starts at 0 and adds each mark: 0 + 91, then 91 + 78, then 169 + 85. The product starts at 1. reduce(Integer::max) has no identity, so it returns an Optional. The last example builds a string of first letters.

reduce folds the elements together two at a time, starting from the identity

Example: All Three Together

AllThree.java▶ Try it
import java.util.List;
 
public class AllThree {
    record Student(String name, int marks) { }
 
    public static void main(String[] args) {
        List<Student> students = List.of(
            new Student("Navika", 91), new Student("Srihitha", 38),
            new Student("Advitha", 85), new Student("Anamaneni", 72));
 
        int passedTotal = students.stream()
            .filter(s -> s.marks() >= 50)
            .map(Student::marks)
            .reduce(0, Integer::sum);
        System.out.println("Total marks of those who passed: " + passedTotal);
    }
}
Output
Total marks of those who passed: 248

Explanation: filter keeps the three students who passed, map turns each into their marks, and reduce adds them: 91 + 85 + 72 = 248.

Common Mistakes

Using a wrong identity, such as reduce(1, Integer::sum). The identity must not change the result: 0 for addition, 1 for multiplication, "" for joining.

Calling get() on reduce without an identity on a possibly empty stream. Use orElse.

Using map when the function returns a stream. That gives a stream of streams; use flatMap.

Interview Corner

What is the difference between map and filter?

filter takes a Predicate and keeps only the elements that satisfy it, so it can reduce the number of elements but doesn't change them. map takes a Function and transforms each element into a new value, possibly of a different type, so the number of elements stays the same.

Practice

  1. Easy: From a list of prices, keep those under 500, add 18% tax to each, and print the new list.
  2. Harder: Using reduce, find the longest name in a list of names (if two have the same length, keep the first).

Quick check: filter, map and reduce

4 questions
1Which operation can change the number of elements?
2What does map(String::length) produce from a Stream?
3What is the right identity for adding numbers with reduce?
4What does reduce return when no identity is given?

Key Takeaways

Was this tutorial helpful?

Lesson 317 of 482