Java 8 stream.collect(... groupingBy(... mapping(... reduction)))减少BinaryOperator的使用

Rol*_*and 8 java java-8 java-stream collectors

我使用了解决方案groupingBy,mapping并使用reducing 以下问题:在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"))
);
Run Code Online (Sandbox Code Playgroud)

以及以下解决方案:

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);
Run Code Online (Sandbox Code Playgroud)

我有:

map = {23=[a, b, c, d, e], 24=[a, b, c, d, e]}
Run Code Online (Sandbox Code Playgroud)

显然不是我所期待的.我更期待这个:

map = {23=[a, d, e], 24=[b, c]}
Run Code Online (Sandbox Code Playgroud)

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

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

Hol*_*ger 15

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

你需要的是一个可变的减少,可以通过实现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;
        })));
Run Code Online (Sandbox Code Playgroud)

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

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

  • 是的,在顺序上下文中,缩减函数将始终被评估为"f(上一个,下一个)",而"前一个"将是第一个评估的标识值和后续评估的先前结果.所以`(a,b) - > a`总是以身份值结束,而`(a,b) - > b`将使用mapper函数创建的新集.但是在并行评估中,两个参数都可以是先前部分评估的结果,并且由于部分结果可以为空,因此任一参数都可以是标识值,因此使用第二个参数不是可靠的修复. (2认同)