Java 8:按字段对集合进行分组并使用流展平并将集合作为映射值连接?

时间:2016-08-29 21:24:34

标签: java java-8 collectors

我的班级有两个领域:

  • MyKey - 我要分组的关键
  • Set<MyEnum> - 我希望被展平和合并的集合。

我有一个这样的对象的列表,我想要的是获得一个Map<MyKey, Set<MyEnum>,其值使用此键从对象的所有myEnums连接。

例如,如果我有三个对象:

  1. myKey: key1, myEnums: [E1]
  2. myKey: key1, myEnums: [E2]
  3. myKey: key2, myEnums: [E1, E3]
  4. 预期结果应为:

    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;
                            })));
    

    它有两个问题:

    1. 缩小内的HashSet似乎在所有键之间共享。这就是说上面例子的实际运行结果是key1 => [E1, E2, E3], key2 => [E1, E2, E3]。为什么会这样?

    2. 即使这段代码有效,它看起来也很难看,尤其是在减少我必须手动处理构建联合集合的逻辑的部分。有没有更好的方法呢?

    3. 谢谢!

2 个答案:

答案 0 :(得分:6)

请注意,您只创建一个标识对象:new HashSet<MyEnum>()

作为第三个参数提供的BinaryOperator必须是idempotent,与常见的数学运算符相同,例如x = y + z不会更改yz的值。

这意味着你需要合并两个输入集ab,而不需要更新。

此外,使用枚举时,您应该使用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())
  );