Java 8 stream.collect(... groupingBy(... mapping(... reduction)))减少BinaryOperator-usage

时间:2017-03-23 16:28:10

标签: java java-8 java-stream collectors

我使用groupingBymappingreducing来解决问题 对于以下问题:Elegantly create map with object fields as key/value from object stream in Java 8。总结的目标是获得一张以年龄为关键的地图,以及一个人的爱好为Set

我提出的解决方案之一(不好,但不是重点)有一种奇怪的行为。

使用以下列表作为输入:

List<Person> personList = Arrays.asList(
     new Person(/* name */ "A", /* age */ 23, /* hobbies */ asList("a")),
     new Person("BC", 24, asList("b", "c")),
     new Person("D", 23, asList("d")),
     new Person("E", 23, asList("e"))
);

以及以下解决方案:

Collector<List<String>, ?, Set<String>> listToSetReducer = Collectors.reducing(new HashSet<>(), HashSet::new, (strings, strings2) -> {
  strings.addAll(strings2);
  return strings;
});
Map<Integer, Set<String>> map = personList.stream()
                                          .collect(Collectors.groupingBy(o -> o.age, 
                                                                         Collectors.mapping(o -> o.hobbies, listToSetReducer)));
System.out.println("map = " + map);

我得到了:

map = {23=[a, b, c, d, e], 24=[a, b, c, d, e]}
显然不是我所期待的。我更期待这一点:

map = {23=[a, d, e], 24=[b, c]}

现在,如果我只是将二元运算符(减少收集器的)(strings, strings2)的顺序替换为(strings2, strings),我得到了预期的结果。那我在这里想念的是什么? 我误解了reducing - 收藏家了吗?或者我错过了哪些文档片段,这显然表明我的用法没有按预期工作?

如果重要的话,Java版本是1.8.0_121。

1 个答案:

答案 0 :(得分:13)

减少不应该修改传入的对象。在您的情况下,您正在修改应该是标识值的传入HashSet并将其返回,因此所有组都将具有相同的HashSet实例,其中包含所有值。

您需要的是Mutable Reduction,可以通过Collector.of(…)实施,就像已经使用预建的收藏家Collectors.toList()Collectors.toSet()等实施的那样。

Map<Integer, Set<String>> map = personList.stream()
    .collect(Collectors.groupingBy(o -> o.age,
        Collector.of(HashSet::new, (s,p) -> s.addAll(p.hobbies), (s1,s2) -> {
            s1.addAll(s2);
            return s1;
        })));

我们需要一个自定义收集器的原因是,Java 8没有Java {9}将要引入的flatMapping收集器。有了这个,解决方案将如下所示:

Map<Integer, Set<String>> map = personList.stream()
    .collect(Collectors.groupingBy(o -> o.age,
        Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet())));