You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确将流分组到自定义POJO?现有实现优化方案探讨

问题分析与优化方案

首先得指出你当前代码的核心问题:测试数据不符合你描述的业务场景,同时分组逻辑可以更高效简洁。

你提到实体的标量属性会重复出现,但你的测试代码里同一个id生成了不同的name(比如id=1对应name_a1和name_b1),而Department类的equals/hashCode是基于id和name生成的,这就导致同一个业务实体被当成了不同的分组键,自然没法合并。另外原实现分两次流处理,中间生成了冗余的集合,效率和可读性都有优化空间。


第一步:修正测试数据(符合业务场景)

先把测试数据改成同一实体的标量属性一致:

for(int i=1;i<=50;i++) {
    // 同一个id对应相同的name,符合"实体属性重复"的业务场景
    summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g1", 3 ) );
    summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g2", 9 ) );
}

优化方案1:用Collectors.toMap一步完成合并

直接通过toMap的合并器逻辑,在流处理中完成实体分组和统计Map的合并,不需要额外遍历:

private static void optimizedGrouping() {
    Gson g = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
    List<DepartmentSummaryRow> summaries = new ArrayList<>();
    // 修正后的测试数据
    for(int i=1;i<=50;i++) {
        summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g1", 3 ) );
        summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g2", 9 ) );
    }

    Collection<Department> departments = summaries.stream()
            .collect(Collectors.toMap(
                    // 分组键:用id+name构建Department(保证同一实体的键一致)
                    row -> new Department(row.getId(), row.getName()),
                    // 初始值:为当前行创建单个group的统计Map
                    row -> {
                        Map<String, Integer> map = new HashMap<>();
                        map.put(row.getGroupName(), row.getGroupMembersCount());
                        return map;
                    },
                    // 合并器:把两个Map的统计值累加(如果有重复groupName则求和)
                    (map1, map2) -> {
                        map2.forEach((k, v) -> map1.merge(k, v, Integer::sum));
                        return map1;
                    }
            ))
            .entrySet().stream()
            // 把合并后的统计Map绑定到Department实例
            .map(entry -> {
                entry.getKey().getGroupCounts().putAll(entry.getValue());
                return entry.getKey();
            })
            .collect(Collectors.toList());

    System.out.println(g.toJson(departments));
}

优化方案2:自定义Collector(更优雅的并行友好实现)

如果需要支持并行流,或者想让逻辑更内聚,可以自定义Collector直接生成目标Department对象:

// 自定义Collector:从DepartmentSummaryRow生成Department
private static Collector<DepartmentSummaryRow, ?, Department> departmentCollector() {
    return Collector.of(
            // 供应器:创建临时容器,存储Department和待合并的统计数据
            () -> new AbstractMap.SimpleEntry<>(new Department(0, ""), new HashMap<String, Integer>()),
            // 累加器:处理每一行数据,初始化实体属性+累加统计
            (entry, row) -> {
                Department dept = entry.getKey();
                // 第一次处理该实体时初始化属性
                if (dept.getId() == 0) {
                    dept.setId(row.getId());
                    dept.setName(row.getName());
                }
                // 合并group统计(重复groupName自动累加)
                entry.getValue().merge(row.getGroupName(), row.getGroupMembersCount(), Integer::sum);
            },
            // 组合器:并行流时合并两个容器的统计数据
            (entry1, entry2) -> {
                Department dept1 = entry1.getKey();
                Department dept2 = entry2.getKey();
                if (!dept1.equals(dept2)) {
                    throw new IllegalArgumentException("无法合并不同部门的数据");
                }
                entry2.getValue().forEach((k, v) -> entry1.getValue().merge(k, v, Integer::sum));
                return entry1;
            },
            // 终结者:把统计数据写入Department的groupCounts
            entry -> {
                entry.getKey().getGroupCounts().putAll(entry.getValue());
                return entry.getKey();
            }
    );
}

// 使用自定义Collector的分组逻辑
private static void customCollectorGrouping() {
    Gson g = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
    List<DepartmentSummaryRow> summaries = new ArrayList<>();
    for(int i=1;i<=50;i++) {
        summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g1", 3 ) );
        summaries.add( new DepartmentSummaryRow(i, "name_"+i, "g2", 9 ) );
    }

    Collection<Department> departments = summaries.stream()
            .collect(Collectors.groupingBy(
                    row -> new Department(row.getId(), row.getName()),
                    LinkedHashMap::new, // 保留插入顺序
                    departmentCollector()
            ))
            .values();

    System.out.println(g.toJson(departments));
}

关键注意事项

  1. 实体equals/hashCode的正确性:你的Department类已经用@EqualsAndHashCode.Exclude排除了groupCounts,这部分是对的,确保分组只依赖实体的核心标量属性。
  2. 输入数据的一致性:必须保证同一实体的所有行,其标量属性(id、name)完全一致,如果是SQL查询导致的属性不一致,需要调整SQL(比如用DISTINCT或者确保关联时不会重复覆盖实体属性)。
  3. 性能优化:优化后的方案避免了中间List的创建和二次遍历,在数据量大的场景下会有明显的性能提升。

内容的提问来源于stack exchange,提问作者Teddy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 19:47:45