Java 8 stream.collect( ... groupingBy ( ... mapping( ... reducing ))) 减少二元运算符的使用
Java 8 stream.collect( ... groupingBy ( ... mapping( ... reducing ))) reducing BinaryOperator-usage
我尝试了一个使用 groupingBy
、mapping
和 reducing
的解决方案
回答以下问题:。总结一下目标是得到一个地图,年龄为key,一个人的爱好为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
-collector?或者我遗漏了哪篇文档,明显表明我的使用没有按预期工作?
Java 版本是 1.8。0_121 如果重要的话。
Reduction 不应该修改传入的对象。在您的情况下,您正在修改应该是身份值的传入 HashSet
和 return 它,因此所有组都将具有相同的 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 没有 flatMapping
收集器,Java 9 将要介绍。这样,解决方案将如下所示:
Map<Integer, Set<String>> map = personList.stream()
.collect(Collectors.groupingBy(o -> o.age,
Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet())));
我尝试了一个使用 groupingBy
、mapping
和 reducing
的解决方案
回答以下问题: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
-collector?或者我遗漏了哪篇文档,明显表明我的使用没有按预期工作?
Java 版本是 1.8。0_121 如果重要的话。
Reduction 不应该修改传入的对象。在您的情况下,您正在修改应该是身份值的传入 HashSet
和 return 它,因此所有组都将具有相同的 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 没有 flatMapping
收集器,Java 9 将要介绍。这样,解决方案将如下所示:
Map<Integer, Set<String>> map = personList.stream()
.collect(Collectors.groupingBy(o -> o.age,
Collectors.flatMapping(p -> p.hobbies.stream(), Collectors.toSet())));