如何使用 Stream API 展平此 List<C1>,其中 C1 包含单个元素的 C2 集合?

How to flatten this List<C1> where C1 contains a collection of C2 of a single element using Stream API?

我有这样的结构(简化):

class C1 {
    Integer id;
    Set<C2> c2Set;
    public C1 add(C2 c2) {
        c2Set.add(c2);
        return this;
    }
    //equals and hashcode methods based on id field.
    @Override public String toString() {
        return "C1: {" + id + ", " + c2Set + "}";
    }
}
class C2 {
    Integer id;
    //equals and hashcode methods based on id field.
    @Override
    public String toString() {
        return "C2: " + id;
    }
}

为了填充这个结构,我执行了以下查询:

SELECT c1.id, c2.id
FROM tablex c1
    INNER JOIN tablex_tabley d ON d.c1id = c1.id
    INNER JOIN tabley c2 ON c2.id = d.c2id
ORDER BY c1.id;

这是直接从ResultSet(使用JDBI)逐行读取的。读取的数据结构类似于:

List<C1> c1List = asList(
    new C1(1).add(new C2(1)),
    new C1(1).add(new C2(2)),
    new C1(2).add(new C2(1))
);

我需要将此列表展平,使其看起来像这样创建的:

List<C1> c1List = asList(
    new C1(1).add(new C2(1)).add(new C2(2)),
    new C1(2).add(new C2(1))
);

目前我使用这段代码来完成这项工作:

Map<C1, C1> c1Map = new LinkedHashMap<>();
for (C1 c1 : c1List) {
    if (!c1Map.containsKey(c1)) {
        c1Map.put(c1, c1);
    } else {
        C1 prevC1 = c1Map.get(c1);
        for (C2 c2 : c1.c2Set) {
            prevC1.add(c2);
        }
    }
}
List<C1> c1ListReduce = new ArrayList<>(c1Map.values());
//just to check the results
System.out.println(c1ListReduce);

如何使用 Java 8 个流实现同样的效果?我找不到一种方法来将属于 C1 实例和 return 一个 List<C1>(或任何其他集合,可能是一个 Set<C1>)的所有 C2 实例分组,在不知道我已经访问过的元素并检索它们以添加它们。

看起来您所要做的就是按共同点 id 对元素进行分组,然后将它们合并为一个 C1:

List<C1> newC1List = c1List.stream()
        .collect(Collectors.groupingBy(C1::getId/*getters should be implementer*/))
        .entrySet()
        .stream()
        .map(entry -> /*constructor should be implemented*/
                new C1(entry.getKey(), entry.getValue().stream().flatMap(c1 -> c1.getC2Set().stream()).collect(Collectors.toSet())))
        .collect(toList());

让我们逐步解释这段代码:

  1. 第一个 collect 行正在创建新的 Map,它将 id 映射到具有此 id 的元素列表。

  2. 在接下来的两行中,我们将创建 Stream 对(实际上是 Map.Entry),其中键是 id,值是C1 的列表,其中有这个 id.

  3. 下一个操作是将值中的所有 C1 合并为一个 C1 (Map.Entry<Integer, List<C1>> -> C1)。 id取自entry的key,虽然我可以用List的第一个元素的id,但是看起来比较丑:)

    所以,元素的id就是entry.getKey(),而c2Set是将C1里面的所有Set合并为一个Set。使用 StreamflatMap 操作可以轻松完成此操作(Haskell 的 concatMap 模拟)。

  4. 我用 collect 方法将 Stream 转换为 List

本着 Dmitry Ginzburg 的相同精神,但实施方式不同。

List<C1> list = c1List.stream()
                      .collect(toMap(c -> c.id, c -> c.c2Set, (set1, set2) -> Stream.concat(set1.stream(), set2.stream()).collect(toSet())))
                      .entrySet()
                      .stream()
                      .map(e -> new C1(e.getKey(), e.getValue()))
                      .collect(toList());

它使用 toMap() 收集器。从 List<C1>,您创建一个 Map<Integer, Set<C2>>,将每个 C1 的 id 映射到它的集合 c2Set。如果您有一个相似的 ID,则将这两个值(即集合)合并到另一组新集合中。

最后您获得条目集并将每个条目映射到一个新的 C1 实例,然后将所有实例收集到一个列表中。

(我假设有一个接受 Set 作为参数的构造函数)