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.
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?
Reveal 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.
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?
- 1Test 1
1 divided by 2 leaves remainder 1, so the predicate returns false. 1 is dropped.
- 2Test 2
2 leaves remainder 0, so the predicate returns true. 2 is kept, unchanged.
- 3Test 3
3 leaves remainder 1, so it is dropped.
- 4Test 4
4 leaves remainder 0, so it is kept, unchanged.
- 5Test 5
5 leaves remainder 1, so it is dropped.
- 6Test 6
6 leaves remainder 0, so it is kept, unchanged.
The new stream contains 2, 4, 6. Three values were removed and none of the kept values were changed.
What a Predicate Must Decide
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.
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.
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.
| Question | Filter | Map |
|---|---|---|
| Question it answers | Should this value be kept? | What should this value become? |
| What the operation receives | A predicate returning true or false | A Function producing an output |
| Effect on each kept value | Value is not changed | Value is replaced by the function output |
| Effect on number of values | Can drop values, so the stream can get smaller | Count stays the same, one output per input |
| Typical use in this article | Keep only even numbers | Square 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.
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);- 1The list starts as 1, 2, 3, 4, 5, 6.
- 2The filter stage tests each value. 2, 4, and 6 pass unchanged; 1, 3, and 5 are dropped.
- 3The map stage receives 2, 4, 6 and emits 4, 16, 36, one square per input.
- 4The 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
[4, 16, 36]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 likeFiltering 1 through 6 for evens and expecting 4, 8, 12
Why it failsA filter only decides whether a value stays. Kept values are exactly the values they were in the original stream.
Do this insteadThe filter over 1 through 6 keeps 2, 4, 6 unchanged.
Common mistake Expecting map to remove values
What it looks likeMapping 1 through 6 with squaring and expecting only 4, 16, 36
Why it failsMap produces exactly one output per input, so the count stays the same.
Do this insteadMapping 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 failsA 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 failsEach stage receives what the previous stage emitted, so the order changes which values reach the map.
Do this insteadTrace the pipeline from left to right, one stage at a time.
Tracing a Pipeline on Your Own
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.