对groupingBy的结果做缩减(flatMap a groupingBy result)

收集器与多级缩减示例。

IntBox.java

public class IntBox {

    private String name;
    private List<Integer> nums = new ArrayList<>();

    // set get constructor 略
}

对于类似IntBox(有属性为集合)的对象的集合List<IntBox>,常有需要获得Map<String, List<Integer>>这样的数据结构。

考虑到name可能重复,所以不可以使用toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper),使用groupingBy也无法获得所需数据结构。

Map<String, List<Integer>> collect = dataList.stream().collect(toMap(IntBox::getName, IntBox::getNums));
Map<String, List<IntBox>> collect = dataList.stream().collect(groupingBy(IntBox::getName));
Map<String, List<List<Integer>>> collect = dataList.stream().collect(groupingBy(IntBox::getName, mapping(IntBox::getNums, toList())));

此时可以通过提供mergeFunction,来使用toMap完成转换。
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction)

Map<String, List<Integer>> collect2 = dataList.stream().collect(toMap(IntBox::getName, IntBox::getNums,
        (v1, v2) -> {
            v1.addAll(v2);
            return v1;
        }));

但是mergeFunction只能返回List,并且mergeFunction会改变原有IntBox对象中集合属性nums中的数据。

可以优化为进行groupingBy操作,并且采用downstream做转换,这样会更加灵活一点。
核心在于Collector.of,如果需要对结果进行去重,可以提供HashSet,结果就是Map<String, Set<Integer>>
groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream)

Map<String, List<Integer>> collect = dataList.stream().collect(groupingBy(IntBox::getName, mapping(IntBox::getNums,
        Collector.of(ArrayList::new, List::addAll, (v1, v2) -> {
            v1.addAll(v2);
            return v1;
        }))));

Map<String, Set<Integer>> collect = dataList.stream().collect(groupingBy(IntBox::getName, mapping(IntBox::getNums,
        Collector.of(HashSet::new, Set::addAll, (v1, v2) -> {
            v1.addAll(v2);
            return v1;
        }))));

上面都是针对于JDK 8的情况,在JDK 9中可以通过flatMapping,一个新的收集器,来实现同样的需求。

Map<String, List<Integer>> collect = dataList.stream()
                .collect(groupingBy(IntBox::getName, flatMapping(x -> x.getNums().stream(), toList())));