- 1.1. From Iterating to Stream Operations
- 1.2. Stream Creation
- 1.3. The filter, map, and flatMap Methods
- 1.4. Extracting Substreams and Combining Streams
- 1.5. Other Stream Transformations
- 1.6. Simple Reductions
- 1.7. The Optional Type
- 1.8. Collecting Results
- 1.9. Collectors
- 1.10. Reduction Operations
- 1.11. Gatherers
- 1.12. Primitive Type Streams
- 1.13. Parallel Streams
1.3. The filter, map, and flatMap Methods
A stream transformation produces a stream whose elements are derived from those of another stream. You have already seen the filter transformation that yields a new stream with those elements that match a certain condition. Here, we transform a stream of strings into another stream containing only long words:
List<String> wordList = . . .; Stream<String> longWords = wordList.stream().filter(w -> w.length() > 12);
The parameter type of filter is Predicate<T>—that is, a function from T to boolean.
Often, you want to transform the values in a stream in some way. Use the map method and pass the function that carries out the transformation. For example, you can transform all words to lowercase like this:
Stream<String> lowercaseWords = words.stream().map(String::toLowerCase);
Here, we used map with a method reference. Often, you will use a lambda expression instead:
Stream<Character> firstCodeUnits = words.stream().map(s -> s.charAt(0));
The resulting stream contains the first code unit of each word.
When you use map, a function is applied to each element, yielding a new stream of the returned values. Now consider the situation where the returned values are themselves streams. The following method yields a stream of all grapheme clusters of a string.
public static Stream<String> graphemeClusters(String s) {
return Stream.of(s.split("\\b{g}"));
}
For example, graphemeClusters("Ahoy 🏴☠️") is a stream of strings "A", "h", "o", "y", " ", and "🏴☠️". (Note that the flag consists of multiple char values.)
Now let's map the graphemeClusters method on a stream of strings:
List<String> wordList = List.of(. . ., "your", "boat", . . .); Stream<Stream<String>> result = wordList.stream().map(w -> graphemeClusters(w));
You will get a stream of streams, like [. . . ["y", "o", "u", "r"], ["b", "o", "a", "t"], . . .]. To flatten it out to a single stream [. . . "y", "o", "u", "r", "b", "o", "a", "t", . . .], use the flatMap method instead of map:
Stream<String> flatResult = words.stream().flatMap(w -> graphemeClusters(w));
// Calls graphemeClusters on each word and flattens the results
Sometimes, it is inefficient to produce a stream for each result sequence. The mapMulti method offers an alternative. Instead of producing a stream of results, you generate the results and pass them to a collector—an object of a class implementing the functional interface Consumer. For each result, invoke the collector's accept method.
Let's do this with an example. The following loop iterates over the grapheme clusters of a string s:
BreakIterator iter = BreakIterator.getCharacterInstance();
. . .
iter.setText(s);
int start = iter.first();
int end = iter.next();
while (end != BreakIterator.DONE) {
String gc = s.substring(start, end);
start = end;
end = iter.next();
// Do something with gc
}
When calling mapMulti, you provide a function that is invoked with the stream element and the collector. In your function, pass your results to the collector.
Stream<String> result = words.stream().mapMulti((s, collector) -> {
iter.setText(s);
int start = iter.first();
int end = iter.next();
while (end != BreakIterator.DONE) {
String gc = s.substring(start, end);
start = end;
end = iter.next();
collector.accept(gc);
}
});
