将流分组为 POJO 的正确方法

Posted

技术标签:

【中文标题】将流分组为 POJO 的正确方法【英文标题】:Right way to group a stream into POJOs 【发布时间】:2021-12-06 09:40:50 【问题描述】:

我有一个汇总行列表,其中每个实体有几行,其中重复了实体的一些标量属性,并且还有两个额外的列 GroupName 和 GroupCount 是唯一的。

基本上这是一个 SQL 连接的输出,实体数据是重复的,并且有一个唯一的组名,以及它在每一行中的计数。

我想将其流式传输并将其收集到一个实体 Dto 中,该实体 Dto 具有实体属性以及用于合并组统计信息的 Map。

我尝试了使用 Collectors.groupingBy 的实现,但它仍然看起来不正确。

    @Data
    @AllArgsConstructor
    public static class DepartmentSummaryRow
        private int id;
        private String name;
        private String groupName;
        private int groupMembersCount;
    
    @Data
    @AllArgsConstructor
    public static class Department
        private int id;
        private String name;
        @EqualsAndHashCode.Exclude
        private final Map<String, Integer> groupCounts = new HashMap<>();
    
    
    public static void main(String[] args) 
        grouping();
    
    
    private static void grouping() 
        Gson g = new GsonBuilder().setPrettyPrinting().disablehtmlEscaping().create();
        
        //Test data
        List<DepartmentSummaryRow> summaries = new ArrayList<>();
        for(int i=1;i<=50;i++) 
            summaries.add( new DepartmentSummaryRow(i, "name_a"+i, "g1", 3 ) );
            summaries.add( new DepartmentSummaryRow(i, "name_b"+i, "g2", 9 ) );
        
        

        //Just group the summary rows
        Map<Department, List<DepartmentSummaryRow>> departmentsToSummaries = summaries
                                                                    .stream()
                                                                    .collect(
                                                                            Collectors.groupingBy( 
                                                                                    (summary)-> return new Department(summary.id, summary.name); , 
                                                                                    LinkedHashMap::new, 
                                                                                    Collectors.toList()
                                                                            )
                                                                    );
        
        //Merge the info into the departments
        departmentsToSummaries.forEach( (entity, sumaryRow)-> 
            entity.groupCounts.putAll( 
                    sumaryRow.stream().collect( 
                                Collectors.groupingBy( 
                                    DepartmentSummaryRow::getGroupName, 
                                    Collectors.summingInt( DepartmentSummaryRow::getGroupMembersCount ) 
                                ) 
                            ) 
                ) ;
             );
        
        System.out.println( g.toJson( departmentsToSummaries.keySet() ) );
    

我正在寻找一些想法,以便更好地实现将流分组到自定义 POJO 中。任何的意见都将会有帮助。谢谢!

(注意:这本身有一些错误..由于某种原因,我的 POJO 的第一个分组根本没有分组..这很奇怪,因为它具有良好的哈希码和 Lombok 提供的等于)

编辑: 这是输入的样子:

[
   "id": 1, "name": "name_a1", "groupName": "g1", "groupMembersCount": 3 , 
   "id": 1, "name": "name_b1", "groupName": "g2", "groupMembersCount": 9 , 
   "id": 2, "name": "name_a1", "groupName": "g1", "groupMembersCount": 3 , 
...
]

这是预期的结果:

[ 
   "id": 1, "name": "name_a1", "groupCounts":  "g1": 3, "g2": 9  , 
   "id": 2, "name": "name_a2", "groupCounts":  "g1": 3, "g2": 9  ,
...
]

【问题讨论】:

你能展示一个最终的 json 应该是什么样子的示例吗? @always_a_rookie 添加 【参考方案1】:

主要问题是只有在summary.idsummary.name 值不同)完成分组时才能检索到预期结果,那么第一个匹配的DepartmentSummaryRow 的名称应该应用于剩余的Department

因此,将nameequalshashCode 中排除在Department 中的小修复应该可以解决问题:

@Data
@AllArgsConstructor
public static class Department 
    private int id;
    @EqualsAndHashCode.Exclude
    private String name;
    @EqualsAndHashCode.Exclude
    private final Map<String, Integer> groupCounts = new HashMap<>();


但是,最好将Collectors.toMapmerge 函数和Supplier&lt;Map&gt; 一起使用以达到类似的结果,而不使用Department 作为映射键:

List<Department> result = new ArrayList<>(
    summaries
        .stream() // Stream<DepartmentSummaryRow>
        .collect(Collectors.toMap(
                DepartmentSummaryRow::getId, // int id as key
                SOGroup::create,             // value: Department
                SOGroup::merge,              // merge departments by id
                LinkedHashMap::new           // keep insertion order
        ))
        .values()
);

result.forEach(System.out::println);

需要实现几个实用方法:

static Department create(DepartmentSummaryRow row) 
    Department dept = new Department(row.getId(), row.getName());
    dept.getGroupCounts().put(row.getGroupName(), row.getGroupMembersCount());
    return dept;


static Department merge(Department dept1, Department dept2) 
    dept2.getGroupCounts().forEach(
        (k, v) -> dept1.getGroupCounts().merge(k, v, Integer::sum)
    );
    return dept1;

输出:

[
    "id":1,"name":"name_a1","groupCounts":"g1":3,"g2":9,
    "id":2,"name":"name_a2","groupCounts":"g1":3,"g2":9,
...
    "id":49,"name":"name_a49","groupCounts":"g1":3,"g2":9,
    "id":50,"name":"name_a50","groupCounts":"g1":3,"g2":9
]

【讨论】:

以上是关于将流分组为 POJO 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

如何正确地将复杂属性 (ArrayList<POJO>) 设置为 GWT BaseTreeModel?序列化问题

确定分组自定义单元格的 UITableViewCell 宽度的正确方法?

jsonschema2pojo-maven-plugin 未正确生成枚举

如何正确分组我的 LINQ 查询?

正确的分组复选框和单选按钮的方法

如何通过正确的 Crashlytics 错误分组获得更好的 RxJava 堆栈跟踪