Java 8: Accumulate the elements of a Stream using Collectors
In this article I want to focus on the different ways of accumulating the elements of a Stream using Collectors.
This is made possible by using
collect — a method in the Stream interface.
collect takes a
Collector, which is an interface for reduction operations.
Today, I'll take a look at
Collectors, which contains several implementations of the
To get familiar with these
Collector implementations, let’s play around with a stream of articles —
Stream to a
Let’s start with the absolute basic — converting a
Stream to a
That’s it — you just use
Collectors.toList which returns a collector that is sent to the
Stream to a
Stream to a
Map is almost as easy as converting to a
To see how it’s done, let’s convert the stream of articles to a map where the title is the key and the article is the value.
This time we use
Collectors.toMap, which takes a function for creating the keys and a function for creating the values.
In our example we use
Article::getTitle to say that we want the titles as keys.
For the value mapping, we use
Function.identity. This simply returns a function that always returns its input parameter — in our case the
Article — as output.
Next, let’s look at how we can calculate averages.
Let’s say that each article contains the number of words in the article text, and we want to check how many words our articles contain in average.
To do this we use the
averageInt which takes a
ToIntFunction where we can map to the count of words.
This arithmetic collector also exists for
long as well.
While we’re at it, let’s look at another arithmetic collector.
This time let’s look at how we can calculate the total number of words in all our articles combined.
Now that we’ve looked at how we can do arithmetic operations on our streams, let’s move on by looking at how we can join strings.
Say, we would like to collect all the titles in one string.
To do this, let’s use
Joining comes in a few different versions.
articles.map(Article::getTitle) .collect(Collectors.joining()); // Resulting string: title1title2
This version simply joins the strings together.
However, this isn’t very readable, so let’s look at another variant of the
joining method where we can set a delimiter.
articles.map(Article::getTitle) .collect(Collectors.joining(", ")); // Resulting string: title1, title2
That’s better, but there is actually one more version where you also can add a prefix and suffix to the resulting string.
articles.map(Article::getTitle) .collect(Collectors.joining(", ", "Articles: ", ".")); // Resulting string: Articles: title1, title2.
Get an element that is maximum or minimum according to a
You also can find an element that is maximum or minimum according to a
Let’s say you want to get the article containing the most words.
Comparator<Article> wordCountComparator = (a1, a2) -> a1.getWordCount() - a2.getWordCount(); articles.collect(Collectors.maxBy(wordCountComparator));
Now if we want to find the article containing the least words, we could use
Comparator<Article> wordCountComparator = (a1, a2) -> a1.getWordCount() - a2.getWordCount(); articles.collect(Collectors.minBy(wordCountComparator));
Partition your stream
Now let’s look at a really cool collector that is partitioning your stream based on a predicate.
Let’s say we would like to split the articles in two based on the count of words being more or less than 1000 words.
articles .collect(Collectors.partitioningBy(article -> article.getWordCount() > 1000));
This will result in a map with two entries. One with the key true which contains the articles that satisfied the predicate and one with key false with the articles that didn’t satisfy the predicate.
Do additional work on the result of a collector
You may want to do some more work on the result of a collector.
Let’s say you want to add the average word count to a string.
Then you can use
collectingAndThen to append a function that will be executed on the result of the collector.
articles.collect( Collectors.collectingAndThen( Collectors.averagingInt(Article::getWordCount), average -> "Average count: " + average));
Create a map by grouping
groupBy is a simple yet powerful way to collect your stream into a map.
Let’s look at two variants of
The first one simply requires to say what you want to group your elements by.
To see how this works, let’s group our data based on an articles tag.
This will return a map with tags mapping to a list of the associated articles.
Let’s now say we want to have the tags mapping to the total number of words of all the articles for a given tag.
articles.collect( Collectors.groupingBy( Article::getTag, Collectors.summingInt(Article::getWordCount)));
In this version we add an extra collector where we say we want to reduce the associated articles into the total count of words.
Map then collect
If you’re familiar with streams, you know about the
So in a scenario where you would like to create a list of the titles of your articles, you could solve this by first mapping to the titles before collecting them using
articles .map(Article::getTitle) .collect(Collectors.toList());
Another option is to use
This may seem like an unnecessary collector, since you got the
map function, but it actually could be quite helpful.
Consider the following.
We want to group our stream of articles by the tag, but we only want to keep the titles in the values.
To solve this, we need to keep the whole Article object long enough to do the grouping, but map to titles afterwards.
This is where
mapping comes handy.
articles.collect( Collectors.groupingBy( Article::getTag, Collectors.mapping(Article::getTitle, Collectors.toList())));
mapping we’re able to first do grouping based on the whole Article object, before mapping it to titles.
Make sure to checkout the documentation for a complete list of available collectors.