New Stream Collectors in Java 9

1. Overview

Collectors were added in Java 8 which helped accumulate input elements into mutable containers such as Map, List, and Set.

In this article, we're going to explore two new collectors added in Java 9: Collectors.filtering and Collectors.flatMapping used in combination with Collectors.groupingBy providing intelligent collections of elements.

2. Filtering Collector

The Collectors.filtering is similar to the Stream filter(); it's used for filtering input elements but used for different scenarios. The Stream's filter is used in the stream chain whereas the filtering is a Collector which was designed to be used along with grouping By.

Collectors.filteringStream filter() 有点像,它用于过滤输入元素,但用于不同的场景下。Stream's filter 被用于stream链,而 Collectors.filtering 被设计为与 grouping By结合使用。

With Stream's filter, the values are filtered first and then it's grouped. In this way, the values which are filtered out are gone and there is no trace of it. If we need a trace then we would need to group first and then apply filtering which actually the Collectors.filtering does.

使用 Stream's filter时,输入元素首先会被过滤掉,然后才会被分组。如果我们需要在分组之后才对其进行过滤的话,应该使用 Collectors.filtering

The Collectors.filtering takes a function for filtering the input elements and a collector to collect the filtered elements:

Collectors.filtering 使用函数来过滤和收集过滤后的元素:

@Test
public void givenList_whenSatifyPredicate_thenMapValueWithOccurences() {
List<Integer> numbers = List.of(1, 2, 3, 5, 5);

Map<Integer, Long> result = numbers.stream()
// 此时stream中只有[5, 5]
.filter(val -> val > 3)
// 然后分组统计,这将会失去对过滤掉元素的统计结果,即使为0
.collect(Collectors.groupingBy(i -> i, Collectors.counting()));

// result => { 5:2 }
assertEquals(1, result.size());

result = numbers.stream()
// 先进行分组[ [1], [2], [3], [5, 5] ]
.collect(Collectors.groupingBy(i -> i,
// 然后过滤掉出每组中大于3的值进行统计
Collectors.filtering(val -> val > 3, Collectors.counting())));

// result => { 1:0, 2:0, 3:0, 5:2 }
assertEquals(4, result.size());
}

3. FlatMapping Collector

The Collectors.flatMapping is similar to Collectors.mapping but has a more fine-grained objective. Both the collectors takes a function and a collector where the elements are collected but flatMapping function accepts a Stream of elements which is then accumulated by the collector.

Collectors.flatMappingCollectors.mapping 类似,但是可以实现更细粒度的控制。

Let's see the following model class:

class Blog {
private String authorName;
private List<String> comments;

// constructor and getters
}

Collectors.flatMapping lets us skip intermediate collection and write directly to a single container which is mapped to that group defined by the Collectors.groupingBy:

@Test
public void givenListOfBlogs_whenAuthorName_thenMapAuthorWithComments() {
Blog blog1 = new Blog("1", "Nice", "Very Nice");
Blog blog2 = new Blog("2", "Disappointing", "Ok", "Could be better");
List<Blog> blogs = List.of(blog1, blog2);

Map<String, List<List<String>>> authorComments1 = blogs.stream()
// 先进行分组,然后对每组进行mapping处理,最终结果为
/* result => {
”1“: [ ["Nice", "Very Nice"] ],
"2": [ ["Disappointing", "Ok", "Could be better"] ]
}
*/
.collect(Collectors.groupingBy(Blog::getAuthorName,
Collectors.mapping(Blog::getComments, Collectors.toList())));

assertEquals(2, authorComments1.size());
assertEquals(2, authorComments1.get("1").get(0).size());
assertEquals(3, authorComments1.get("2").get(0).size());

Map<String, List<String>> authorComments2 = blogs.stream()
// 现根据 authorName 分组
.collect(Collectors.groupingBy(Blog::getAuthorName,
// 然后将每组的元素以流的形式处理
/* result => {
"1": ["Nice", "Very Nice"],
"2": ["Disappointing", "Ok", "Could be better"]
}
*/
Collectors.flatMapping(blog ->
blog.getComments().stream(), Collectors.toList())));

assertEquals(2, authorComments2.size());
assertEquals(2, authorComments2.get("1").size());
assertEquals(3, authorComments2.get("2").size());
}

The Collectors.mapping maps all grouped author's comments to the collector's container i.e. List whereas this intermediate collection is removed with flatMapping as it gives a direct stream of the comment list to be mapped to the collector's container.

4. Conclusion

This article illustrates the use of the new Collectors introduced in Java9 i.e. Collectors.filtering() and Collectors.flatMapping() used in combination with Collectors.groupingBy().

These Collectors can also be used along with Collectors.partitioningBy() but it only creates two partitions based on conditions and the real power of the collectors isn't leveraged; hence left out of this tutorial.

The complete source code for the code snippets in this tutorial is available over on GitHub.