使用Java 8流进行复杂聚合

Dan*_*ley 3 java java-8 java-stream

给定一个类项目:

public class Item {
    private String field1;
    private String field2;
    private String field3;
    private Integer field4;

    // getters, constructor...
}
Run Code Online (Sandbox Code Playgroud)

另一个类Group(field1和field2存储Item中的等效字段):

public class Group {
    private String field1;
    private String field2;
}
Run Code Online (Sandbox Code Playgroud)

我有一个List<Item>我需要聚合到以下结构的地图:

Map<Group, Map<Field3, List<Field4>>>
Run Code Online (Sandbox Code Playgroud)

示例数据:

Field1 | Field2 | Field3 | Field4
------ | ------ | ------ | ------
"f1"   | "f2"   | "a"    | 1
"f1"   | "f2"   | "a"    | 2
"f1"   | "f2"   | "a"    | 3
"f1"   | "f2"   | "b"    | 4
"f1"   | "f2"   | "b"    | 5
"f1"   | "f2"   | "c"    | 6
"f1a"  | "f2a"  | "a"    | 7
"f1a"  | "f2a"  | "a"    | 8
Run Code Online (Sandbox Code Playgroud)

预期结果如下:

Group(field1=f1a, field2=f2a)={b=[7, 8]}, Group(field1=f1, field2=f2)={a=[1, 2, 3], b=[4, 5], c=[6]}
Run Code Online (Sandbox Code Playgroud)

到目前为止,我已经能够通过Field1,Field2和Field3进行聚合,这样我就有了以下结构(其中GroupEx表示一个包含Field1,Field2和Field3的POJO):

Map<GroupEx, List<Field4>>
Run Code Online (Sandbox Code Playgroud)

以这种方式聚合的代码是:

Map<GroupEx, List<Integer>> aggregated = items.stream()
    .collect(Collectors.groupingBy(item -> new GroupEx(x.getField1(), x.getField2(), x.getField3())
           , Collectors.mapping(Item::getField4, Collectors.toList())));
Run Code Online (Sandbox Code Playgroud)

我正努力让语法正确,允许我按Field1和Field2分组,然后按照我需要的方式将Field3和Field4分组到地图中.

"长手"语法是:

Map<Group<String, String>, Map<String, List<Integer>>> aggregated = new HashMap<>();
for (Item item : items) {
    Group key = new Group(item.getField1(), item.getField2());
    Map<String, List<Integer>> field3Map = aggregated.get(key);
    if (field3Map == null) {
        field3Map = new HashMap<>();
        aggregated.put(key, field3Map);
    }

    List<Integer> field4s = field3Map.get(item.getField3());
    if (field4s == null) {
        field4s = new ArrayList<>();
        field3Map.put(item.getField3(), field4s);
    }

    field4s.add(item.getField4());
}
Run Code Online (Sandbox Code Playgroud)

有人能告诉我如何实现目标分组吗?

Ale*_* C. 7

这是下游收集器功能的便利之处.

import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;

...

List<Item> list = ....
Map<Group, Map<String, List<Integer>>> map =
    list.stream().collect(groupingBy(i -> new Group(i.getField1(), i.getField2()),
                                     groupingBy(Item::getField3, mapping(Item::getField4, toList()))));
Run Code Online (Sandbox Code Playgroud)

首先,按照Group字段(Map<Group, List<Item>>此时为a)对项目进行分组,然后将每个值(List<Item>)再次映射到地图,然后按field3(Map<Group, Map<Field3, List<Item>>)进行分组.

然后你通过field4映射第二个地图中的值,然后将它们收集到一个列表中,最后得到一个Map<Group, Map<Field3, List<Field4>>.

根据您的输入,它输出:

{Group{field1='f1a', field2='f2a'}={a=[7, 8]}, Group{field1='f1', field2='f2'}={a=[1, 2, 3], b=[4, 5], c=[6]}}
Run Code Online (Sandbox Code Playgroud)