我的班级有两个领域:
MyKey
- 我要分组的关键Set<MyEnum>
- 我希望被展平和合并的集合。我有一个这样的对象的列表,我想要的是获得一个Map<MyKey, Set<MyEnum>
,其值使用此键从对象的所有myEnums连接。
例如,如果我有三个对象:
myKey: key1, myEnums: [E1]
myKey: key1, myEnums: [E2]
myKey: key2, myEnums: [E1, E3]
预期结果应为:
key1 => [E1, E2], key2 => [E1, E3]
我想出了这段代码:
Map<MyKey, Set<MyEnum>> map = myObjs.stream()
.collect(Collectors.groupingBy(
MyType::getMyKey,
Collectors.reducing(
new HashSet<MyEnum>(),
MyType::getMyEnums,
(a, b) -> {
a.addAll(b);
return a;
})));
它有两个问题:
缩小内的HashSet
似乎在所有键之间共享。这就是说上面例子的实际运行结果是key1 => [E1, E2, E3], key2 => [E1, E2, E3]
。为什么会这样?
即使这段代码有效,它看起来也很难看,尤其是在减少我必须手动处理构建联合集合的逻辑的部分。有没有更好的方法呢?
谢谢!
答案 0 :(得分:6)
请注意,您只创建一个标识对象:new HashSet<MyEnum>()
。
作为第三个参数提供的BinaryOperator
必须是idempotent,与常见的数学运算符相同,例如x = y + z
不会更改y
和z
的值。
这意味着你需要合并两个输入集a
和b
,而不需要更新。
此外,使用枚举时,您应该使用EnumSet
,而不是HashSet
。
Map<MyKey, Set<MyEnum>> map = myObjs.stream()
.collect(Collectors.groupingBy(
MyType::getMyKey,
Collectors.reducing(
EnumSet.noneOf(MyEnum.class), // <-- EnumSet
MyType::getMyEnums,
(a, b) -> {
EnumSet<MyEnum> c = EnumSet.copyOf(a); // <-- copy
c.addAll(b);
return c;
})));
<强>更新强>
更短,更简化的版本,在累积结果时不必继续创建新集:
Map<MyKey, Set<MyEnum>> map = myObjs.stream()
.collect(Collectors.groupingBy(
MyType::getMyKey,
Collector.of(
() -> EnumSet.noneOf(MyEnum.class),
(r, myObj) -> r.addAll(myObj.getMyEnums()),
(r1, r2) -> { r1.addAll(r2); return r1; }
)));
答案 1 :(得分:0)
不理想,但使用可变容器使其易于理解。
myObjs.stream()
.collect(groupingBy(MyType::getMyKey)
.entrySet().stream()
.collect(toMap(
Map.Entry::getKey,
e -> e.getValue()
.stream()
.flatMap(v -> v.getMyEnums().stream())
.collect(toSet())
)
Collectors.mapping(Function, Collector)几乎完全适合您想要在这里做的事情,如果只是Collectors.flatMapping
编辑:在Java 9发布之前,this answer中有flatMapping
的便捷实现。有了它我们的解决方案看起来像这样:
myObjs.stream()
.collect(
groupingBy(MyType::getMyKey,
flatMapping(v -> v.getMyEnums().stream(), toSet())
);