[英]Right way to group a stream into POJOs
我有一個匯總行列表,其中每個實體的行數很少,其中重復了實體的一些標量屬性,並且還有兩個唯一的附加列 GroupName 和 GroupCount。
基本上這是一個 SQL 連接的輸出,實體數據是重復的,並且有一個唯一的組名,以及它在每行中的計數。
我想流式傳輸它並將其收集到一個實體 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 提供的 equals)
編輯:這是輸入的樣子:
[
{ "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 } },
...
]
主要問題是,只有通過summary.id
進行分組( summary.name
值不同)才能檢索預期結果,然后第一個匹配的DepartmentSummaryRow
的名稱應該應用於剩余的Department
。
因此,從Department
equals
和hashCode
中排除name
的小修復應該可以解決問題:
@Data
@AllArgsConstructor
public static class Department {
private int id;
@EqualsAndHashCode.Exclude
private String name;
@EqualsAndHashCode.Exclude
private final Map<String, Integer> groupCounts = new HashMap<>();
}
但是,最好使用帶有merge
函數和Supplier<Map>
Collectors.toMap
來實現類似的結果,而不使用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}}
]
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.