Terminal Operations in Java Streams
Filter and map are the two most basic intermediate operations in the Java Stream API, as described in the source.
Predicting a Simple Selection
Stream processing in Java is built from a sequence of values that move through a pipeline. Some operations in that pipeline take a stream in and hand a new stream out. The source material for this article describes these as intermediate operations, because they sit in the middle of the pipeline and change the stream before passing it on. The two most basic intermediate operations it identifies are filter and map.
What do you think happens?
You have a list of numbers from 1 to 6 and want only the even ones. What would you need to tell the stream to do? Is your answer about deciding which values to keep, or about changing the values themselves?
Reveal answer
You tell the stream to keep only the values that pass an even-number test. The kept values are 2, 4, and 6, and each one is exactly the value it was in the original list.
Selecting even numbers is a question of deciding which values survive, not of changing them. That is the job of filter. Map answers a different question, what each value should become, and you will meet it shortly.
Filter and the Predicate Test
A filter applies a Predicate and keeps the values for which the predicate is true, without changing them. A predicate is a test that gives a true or false answer for each value. The filter checks every value in the original stream against that test. Values that pass are placed into the new stream, and values that fail are left out.
The important point is that a filter never edits a value. A kept value is exactly the value it was in the original stream. This is why filter is described as a way to narrow down a stream based on a condition. The stream can only shrink or stay the same size through a filter; it never gains new values and never has its values rewritten.
A predicate does not build a list and does not return values. It only answers yes or no for the value it is given. The filter is the part that uses those yes or no answers to decide what enters the new stream.
Map and the Function That Produces Outputs
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. The source describes map as 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. The number of values stays the same, because each input produces exactly one output. A map never drops a value; it replaces every value with whatever the function returns for it.
- The parameter n is one value from the stream.
- The body n * n is the output the function produces for that value.
- The map applies this rule to every value, so the output stream has the same number of values as the input.
A map changes the contents of the stream: every value is replaced by the output of the function. A filter changes only the number of values, by keeping some and dropping others, and never changes the values it keeps.
Tracing Filter Then Map Together
Filter and map are often combined in one pipeline, with the filter narrowing the values before the map transforms them. 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 the non-null strings is a job for a filter. Converting each one to lower case is a transformation, which is a job for a map.
- The filter keeps only values that are not null, so the null entry is removed.
- The map converts each remaining string to lower case.
- This article describes the pipeline only; it does not cover the operation that finishes a pipeline.
Tracing the strList pipeline
Starting from the values Apple, null, and BANANA, which values does each stage produce, and what does the final stream contain?
- 1Start with three values
The stream holds Apple, null, and BANANA.
- 2Apply the filter
The predicate s != null is true for Apple and BANANA and false for null. Apple and BANANA are kept unchanged; null is left out.
- 3Apply the map
Each remaining string passes through
toLowerCase. Apple becomes apple and BANANA becomes banana. - 4Compare the counts
The filter reduced three values to two. The map kept the count at two, because each value produced exactly one output.
The final stream contains apple and banana. The filter decided which values survived, and the map decided what each survivor became.
Filter Versus 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 |
|---|---|---|
| Takes in | A Predicate | A Function |
| What it decides | Whether each value is kept | What each value becomes |
| Values changed? | No, kept values are unchanged | Yes, every value is replaced by the output |
| Number of values | Can stay the same or go down | Stays the same |
| Example from this article | Keep only non-null strings | Convert each string to lower case |
Filter and map as described in the source material
Mistakes When Reading a Pipeline
Common mistake Assuming a filter changes the values it keeps
What it looks likeFiltering 1 through 6 for evens and expecting 4, 16, and 36 from the filter alone
Why it failsA filter only decides which values survive. The kept values are exactly what they were before.
Do this insteadRead the filter as a yes or no test on each value. Squaring the values is the work of a map.
Common mistake Thinking a map can remove values
What it looks likeExpecting a map that converts strings to lower case to drop the null entry
Why it failsA map applies its function to every value and produces one output per input, so the count stays the same.
Do this insteadRemove values with a filter placed before the map, as in the strList pipeline.
Common mistake Treating the predicate as something that builds a list
What it looks likeExpecting isPrime(n) to return a list of primesWhy it failsThe predicate only answers true or false for one value. The filter gathers the values that pass.
Do this insteadDescribe the predicate as a per-value test, and describe the filter as the step that collects the passing values.
Common mistake Reversing the order of filter and map
Why it failsThe map would run on values that the filter was meant to remove, and the result can differ.
Do this insteadPlace the filter first when narrowing is needed before transforming, as the source describes.
Predicting a Two-Stage Result
A stream holds the integers 1 through 8. The pipeline first keeps only the values greater than 4, then applies a map that doubles each value. Before checking, predict: how many values reach the end, and which values are they?
Hints
- Start by listing the values the filter keeps: those greater than 4.
- The map doubles each kept value and does not drop any of them.
- Count the survivors of the filter first, then check that the map kept the same count.
What do you think happens?
How many values reach the end of the pipeline, and which are they?
Reveal answer
Four values reach the end: 10, 12, 14, and 16.
The filter keeps 5, 6, 7, and 8, unchanged. The map doubles each one, giving 10, 12, 14, and 16. The count stays at four through the map because each input produces exactly one output.
Key Takeaways
- A filter applies a Predicate, a true or false test on each value, and keeps the values that pass without changing them.
- A map applies a Function to every value and produces one output per input, so the values are transformed and the count stays the same.
- Filter decides which values survive; map decides what each value becomes. Both take a stream in and produce a stream out.
- When combined in one pipeline, the filter usually narrows the values first and the map then transforms the survivors.