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

时间:2015-04-09 16:06:02

标签: java java-8 java-stream

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

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实例的所有C2实例进行分组,并返回List<C1>(或任何其他集合,可能是Set<C1>),在这种情况下,如果不知道我已经访问过的元素并检索它们以添加它们。

2 个答案:

答案 0 :(得分:3)

看起来你要做的就是按照公共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映射到具有此Stream的元素列表。
  2. 在接下来的两行中,我们创建Map.Entry对(实际为id),其中键为C1,值为id列表}的,有C1
  3. 下一步操作是将值中的所有C1合并为一个Map.Entry<Integer, List<C1>> -> C1id)。 List取自入口键,但我可以使用id中第一个元素的id,但看起来更难看:)

    因此,entry.getKey()元素为c2Set,而SetC1内所有Set合并为一个flatMap 1}}。使用Stream(来自Haskell的concatMap类似物)的Stream操作可以轻松完成此操作。

  4. 我使用List方法将此collect转换为{{1}}。

答案 1 :(得分:3)

与德米特里·金兹堡的精神相同,但实施方式不同。

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实例,并将所有实例收集到List中。

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