Home > Articles

Streams

This chapter is from the book

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);
    }
});

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.