Concepts / Introduction to the Java Stream API

Introduction to the Java Stream API

Filter and map are the two most basic intermediate operations in the Java Stream API, as described in the source.

  • Programming

Predicting Which Values Survive

A stream is a sequence of values that you process in a pipeline. Some operations in that pipeline take a stream in and hand a new stream out. These are called intermediate operations, because they sit in the middle of the pipeline: they change the stream, and the stream can then be passed on to the next step. The two most basic intermediate operations are filter and map. Before reading on, it helps to notice how they differ, because they answer two different questions.

What do you think happens?

You have a list of numbers from 1 to 6 and you want only the even ones. Would you be deciding which values to keep, or changing the values themselves? What would the stream produce?

  • Keep only the even values: 2, 4, 6, unchanged

  • Change every value into an even number: 2, 2, 4, 4, 6, 6

  • Keep all six values and double each one

Reveal answer
Answer:

Keep only the even values: 2, 4, 6, unchanged

Selecting even numbers is a question about which values to keep, so it is a filter. The kept values are not altered. Doubling or otherwise changing values would be a map, which asks what each value should become.

Tracing a Filter Step by Step

A filter checks every value in the original stream against a test. The test is a predicate: a test that gives a true or false answer for each value it receives. Values that pass the test are placed into the new stream. Values that fail are left out. A value that is kept is exactly the value it was in the original stream. Nothing about it is changed.

Value from streamfor example 3Predicate testis n % 2 equal to 0Kept unchangedpasses to next stageDroppednot in new streamcheckedtruefalse
For each value entering a filter, what does the predicate return, and which values continue downstream?
Worked example

Filtering the numbers 1 to 6 for even values

Apply a predicate that returns true when a number is even to the stream of values 1, 2, 3, 4, 5, 6. Which values appear in the new stream?

  1. 1
    Test 1

    1 divided by 2 leaves remainder 1, so the predicate returns false. 1 is dropped.

  2. 2
    Test 2

    2 leaves remainder 0, so the predicate returns true. 2 is kept, unchanged.

  3. 3
    Test 3

    3 leaves remainder 1, so it is dropped.

  4. 4
    Test 4

    4 leaves remainder 0, so it is kept, unchanged.

  5. 5
    Test 5

    5 leaves remainder 1, so it is dropped.

  6. 6
    Test 6

    6 leaves remainder 0, so it is kept, unchanged.

Result

The new stream contains 2, 4, 6. Three values were removed and none of the kept values were changed.

What a Predicate Must Decide

Definition

A predicate is a test that returns true or false for a single value. A filter needs exactly this kind of answer for each value: keep it, or leave it out. The predicate does not build a new list. It only answers yes or no for the value it is given.

The source gives an example of a boolean-valued function isPrime(n) that tests whether an integer n is prime. Passing that function as the predicate to a filter would produce a stream containing only the prime values from the original stream. The function is not what produces the new stream. The filter produces the stream by applying the function to every value and keeping the ones that return true.

Key point

A filter narrows a stream. It asks only whether each value should stay, and it never changes the values that stay.

Tracing a Map Step by Step

Where a filter asks whether to keep a value, a map asks what to turn it into. A Function is applied to every value in the stream, and the results form the new stream. Map is the operation used to transform the values in a stream. Unlike filter, the new stream does not generally contain the original values. It contains the outputs of the function.

2439416squaresquaresquare
How does each input value become a different output value after map, and does the number of elements change?

In the diagram, each input produces exactly one output. Three values went in and three values came out. The count stays the same because each input produces exactly one output, but every value is different from the one it came from.

Filter and Map Side by Side

The two operations are easy to confuse because both take a stream in and produce a stream out. The difference lies in what happens to each value. A filter decides whether a value is kept, and a kept value is not altered. A map decides what each value becomes, and every value is replaced by the output of the function.

QuestionFilterMap
Question it answersShould this value be kept?What should this value become?
What the operation receivesA predicate returning true or falseA Function producing an output
Effect on each kept valueValue is not changedValue is replaced by the function output
Effect on number of valuesCan drop values, so the stream can get smallerCount stays the same, one output per input
Typical use in this articleKeep only even numbersSquare each number

Filter removes values without changing them. Map changes values without removing them.

Combining Filter and Map in One Pipeline

Filter and map are often combined in one pipeline. The filter narrows the values first, and then the map transforms the values that remain. The source describes a practical case: suppose strList is an ArrayList of String, and you want a stream of all the non-null strings in the list, converted to lower case. Selecting non-null strings is a filter. Converting them to lower case is a map. The code below is an illustrative version written for this article, not code taken from the source.

Code walkthrough
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> result = numbers.stream()
    .filter(n -> n % 2 == 0)
    .map(n -> n * n)
    .collect(Collectors.toList());
System.out.println(result);
  1. 1
    The list starts as 1, 2, 3, 4, 5, 6.
  2. 2
    The filter stage tests each value. 2, 4, and 6 pass unchanged; 1, 3, and 5 are dropped.
  3. 3
    The map stage receives 2, 4, 6 and emits 4, 16, 36, one square per input.
  4. 4
    The collect step is a final step that produces the printed list. This article does not cover it in depth.
  • numbers.stream() starts a pipeline over the six values
  • filter keeps only the values whose remainder after dividing by 2 is 0
  • map replaces each remaining value with its square
  • collect gathers the final stream into a list so the result can be printed
Output
[4, 16, 36]
1, 2, 3, 4, 5, 6original streamFilter evenkeeps 2, 4, 6 unchanged2, 4, 6stream between stagesMap squareone output per input4, 16, 36final resultentersemitsentersemits
When values pass through filter and then map, what does each stage receive and emit, and what is the final result?

Notice the order. The map does not see the values 1, 3, and 5 at all, because the filter removed them before the map stage began. If the order were reversed, the map would square every value first, producing 1, 4, 9, 16, 25, 36, and the filter would then keep the even squares 4, 16, 36. The final output happens to match here, but the map would have done more work on values that were later discarded.

Mistakes When Reading Pipelines

  • Common mistake

    Assuming filter changes the values it keeps

    What it looks like

    Filtering 1 through 6 for evens and expecting 4, 8, 12

    Why it fails

    A filter only decides whether a value stays. Kept values are exactly the values they were in the original stream.

    Do this instead

    The filter over 1 through 6 keeps 2, 4, 6 unchanged.

  • Common mistake

    Expecting map to remove values

    What it looks like

    Mapping 1 through 6 with squaring and expecting only 4, 16, 36

    Why it fails

    Map produces exactly one output per input, so the count stays the same.

    Do this instead

    Mapping 1 through 6 with squaring produces six outputs: 1, 4, 9, 16, 25, 36.

  • Common mistake

    Treating the predicate as the thing that builds the new list

    Why it fails

    A predicate only returns true or false for one value. The filter stage is what assembles the new stream from the values that pass.

  • Common mistake

    Reading a pipeline's stages in the wrong order

    Why it fails

    Each stage receives what the previous stage emitted, so the order changes which values reach the map.

    Do this instead

    Trace the pipeline from left to right, one stage at a time.

Tracing a Pipeline on Your Own

EASY

A stream holds the numbers 1 through 10. The pipeline first filters to keep only the odd numbers, then maps each remaining value by multiplying it by 10. Write the values that reach the end of the pipeline, in order.

Hints
  • Start by listing which of 1 through 10 are odd. The filter keeps those values unchanged.
  • Then apply the map to each kept value separately. Each one becomes ten times itself.
  • Count the outputs. A map never changes how many values there are, so the count should match the number of odd values.

Key Takeaways

  • A filter applies a predicate to each value and keeps the values for which the predicate is true, without changing them.
  • A map applies a Function to each value and produces a stream of the outputs, so every value is transformed and the count stays the same.
  • Filter can make a stream smaller, while map changes values without removing them.
  • In a pipeline, the filter usually runs first to narrow the values, and the map then transforms the values that remain.