【问题标题】:Complex aggregation using Java 8 streams使用 Java 8 流的复杂聚合
【发布时间】:2017-09-26 08:42:23
【问题描述】:

给定一个类Item:

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

    // getters, constructor...
}

还有另一个类Group(field1和field2存储Item的等价字段):

public class Group {
    private String field1;
    private String field2;
}

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

Map<Group, Map<Field3, List<Field4>>>

示例数据:

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

预期结果如下:

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

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

Map<GroupEx, List<Field4>>

这样聚合的代码是:

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

我正在努力获得正确的语法,以允许我按 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());
}

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

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    这就是downstream collectors 功能派上用场的地方。

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

    首先,您按项目的Group 字段(此时您是Map&lt;Group, List&lt;Item&gt;&gt;)对项目进行分组,然后将每个值(List&lt;Item&gt;)再次映射到您按字段3 分组的映射(Map&lt;Group, Map&lt;Field3, List&lt;Item&gt;&gt; )。

    然后您将第二个映射中的值按字段 4 进行映射,并将它们收集到一个列表中,最终得到一个 Map&lt;Group, Map&lt;Field3, List&lt;Field4&gt;&gt;

    根据您的输入,它会输出:

    {Group{field1='f1a', field2='f2a'}={a=[7, 8]}, Group{field1='f1', field2='f2'}={a=[1, 2, 3], b=[4, 5], c=[6]}}
    

    【讨论】:

    • 您还覆盖了Group equalshashCode 对吗?为了在为Map 创建密钥时比较两组。
    • @andreim 是的,否则由项目生成的每个 Group 都将是地图中的唯一键
    • 谢谢亚历克西斯——这正是我所需要的。恼人的是,我已经非常接近了,但被最后的mapping 调用绊倒了,编译器并没有给我太多帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-09
    • 2014-10-21
    • 2019-11-04
    相关资源
    最近更新 更多