将流分组为 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.id
(summary.name
值不同)完成分组时才能检索到预期结果,那么第一个匹配的DepartmentSummaryRow
的名称应该应用于剩余的Department
。
因此,将name
从equals
和hashCode
中排除在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.toMap
与merge
函数和Supplier<Map>
一起使用以达到类似的结果,而不使用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 宽度的正确方法?