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

我使用groupingBymappingreducing解决了以下问题的解决方案: 在Java 8中优雅地创建带有对象字段的映射作为对象流的键/值 。 总结的目标是获得一张以年龄为关键的地图和一个人作为一套的爱好。

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

使用以下列表作为输入:

 List 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, ?, Set> listToSetReducer = Collectors.reducing(new HashSet(), HashSet::new, (strings, strings2) -> { strings.addAll(strings2); return strings; }); Map<Integer, Set> 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。

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

你需要的是一个可变减少 ,它可以通过Collector.of(…)实现,就像已经使用预构建的收集器Collectors.toList()Collectors.toSet()等实现的那样。

 Map> 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没有flatMapping收集器,Java 9将引入它。 有了它,解决方案将如下所示:

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