Java 8:按字段对集合进行分组,并使用流将集合展平并作为映射值加入集合?
Java 8: grouping a collection by a field and flatten and join a collection as mapped value using stream?
我的 class 有两个字段:
MyKey
- 我要分组的键
Set<MyEnum>
- 我想要展平和合并的集合。
我有一个此类对象的列表,我想要的是获得一个 Map<MyKey, Set<MyEnum>
,其中的值是使用此键从对象的所有 myEnum 中加入的。
例如,如果我有三个对象:
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;
})));
它有两个问题:
reducing 中的 HashSet
似乎在所有键之间共享。也就是说,上述示例的实际 运行 结果是 key1 => [E1, E2, E3], key2 => [E1, E2, E3]
。为什么会这样?
即使此代码有效,它看起来也很难看,尤其是在减少部分,我必须手动处理构建连接集合的逻辑。有更好的方法吗?
谢谢!
请注意,您只创建了 一个 身份对象: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; }
)));
不理想,但使用可变容器使其相当容易理解。
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 出来之前, 中有一个方便的 flatMapping
实现。有了它,我们的解决方案如下所示:
myObjs.stream()
.collect(
groupingBy(MyType::getMyKey,
flatMapping(v -> v.getMyEnums().stream(), toSet())
);
我的 class 有两个字段:
MyKey
- 我要分组的键Set<MyEnum>
- 我想要展平和合并的集合。
我有一个此类对象的列表,我想要的是获得一个 Map<MyKey, Set<MyEnum>
,其中的值是使用此键从对象的所有 myEnum 中加入的。
例如,如果我有三个对象:
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;
})));
它有两个问题:
reducing 中的
HashSet
似乎在所有键之间共享。也就是说,上述示例的实际 运行 结果是key1 => [E1, E2, E3], key2 => [E1, E2, E3]
。为什么会这样?即使此代码有效,它看起来也很难看,尤其是在减少部分,我必须手动处理构建连接集合的逻辑。有更好的方法吗?
谢谢!
请注意,您只创建了 一个 身份对象: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; }
)));
不理想,但使用可变容器使其相当容易理解。
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 出来之前,flatMapping
实现。有了它,我们的解决方案如下所示:
myObjs.stream()
.collect(
groupingBy(MyType::getMyKey,
flatMapping(v -> v.getMyEnums().stream(), toSet())
);