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

Java中高效聚合ArrayList数据:按ID汇总分数的最优实现问询

高效实现ID分组的成绩求和方案

需求说明

现有从数据库返回的ArrayList数据(实体类ABCDTO包含id、year、name、rank、Subj、Marks字段),原始数据示例:

id  year    name    rank      Subj      Marks   
1   2025    name1   A       Science     90
1   2025    name1   A       History     90
1   2025    name1   A       Math        90
2   2025    name2   A       Science     80
2   2025    name2   A       History     80
2   2025    name2   A       Math        80

需要转换为:同一id的Marks求和,Subj统一设为"Subject",结果示例:

id    year  name    rank      Subj      Marks   
1   2025    name1   A       Subject     270
2   2025    name2   A       Subject     240

数据量至少1500条,要求高效实现。

现有代码问题分析

你当前的HashMap实现逻辑存在明显问题:

  1. 第一个循环中,每次遍历ABCDTO都新建ResultsBean但未赋值任何字段,且直接用id作为key存入HashMap,会导致同一id的后续数据直接覆盖之前的entry,完全没保留原始数据。
  2. 第二个循环逻辑混乱:将hmDto的value强转为ABCDTO(但实际存的是ResultsBean,会抛出类型转换异常),且判断hmDto.containsKey(entry.getKey())永远为真,没有实现任何求和逻辑。

最优实现方案

方案1:传统HashMap遍历(兼容Java 8及以下)

核心思路:用id作为HashMap的key,value存储聚合后的ResultsBean对象。遍历原始列表时,若key已存在则累加Marks,不存在则初始化对象并赋值基础字段(year/name/rank同一id应一致,取第一条数据即可)。

// 假设ResultsBean的结构:包含id、year、name、rank、Subj、Marks字段
HashMap<String, ResultsBean> resultMap = new HashMap<>();

for (ABCDTO dto : list) {
    String idKey = dto.getId();
    // 检查是否已存在该id的聚合对象
    if (resultMap.containsKey(idKey)) {
        ResultsBean existingBean = resultMap.get(idKey);
        // 累加分数
        existingBean.setMarks(existingBean.getMarks() + dto.getMarks());
    } else {
        // 新建聚合对象并初始化字段
        ResultsBean newBean = new ResultsBean();
        newBean.setId(dto.getId());
        newBean.setYear(dto.getYear());
        newBean.setName(dto.getName());
        newBean.setRank(dto.getRank());
        newBean.setSubj("Subject"); // 统一设置Subject
        newBean.setMarks(dto.getMarks());
        resultMap.put(idKey, newBean);
    }
}

// 转换为最终的ArrayList结果
List<ResultsBean> finalResult = new ArrayList<>(resultMap.values());

该方案时间复杂度为O(n),仅遍历一次原始列表,内存占用可控,适合大数据量场景。

方案2:Java 8+ Stream API(简洁高效)

利用Stream的分组和聚合操作,代码更简洁,底层同样是高效的遍历逻辑:

List<ResultsBean> finalResult = list.stream()
    // 按id分组,key为id,value为对应ABCDTO列表
    .collect(Collectors.groupingBy(ABCDTO::getId))
    .entrySet().stream()
    // 对每个分组的DTO列表转换为ResultsBean
    .map(entry -> {
        String id = entry.getKey();
        List<ABCDTO> dtoList = entry.getValue();
        // 取第一个DTO的基础字段(同一id的year/name/rank应一致)
        ABCDTO firstDto = dtoList.get(0);
        ResultsBean bean = new ResultsBean();
        bean.setId(id);
        bean.setYear(firstDto.getYear());
        bean.setName(firstDto.getName());
        bean.setRank(firstDto.getRank());
        bean.setSubj("Subject");
        // 求和Marks
        int totalMarks = dtoList.stream().mapToInt(ABCDTO::getMarks).sum();
        bean.setMarks(totalMarks);
        return bean;
    })
    .collect(Collectors.toList());

如果担心多次stream遍历的开销,可以改用Collectors.collectingAndThen优化分组后的聚合:

List<ResultsBean> finalResult = list.stream()
    .collect(Collectors.groupingBy(ABCDTO::getId, Collectors.collectingAndThen(
        Collectors.toList(),
        dtoList -> {
            ABCDTO firstDto = dtoList.get(0);
            ResultsBean bean = new ResultsBean();
            bean.setId(firstDto.getId());
            bean.setYear(firstDto.getYear());
            bean.setName(firstDto.getName());
            bean.setRank(firstDto.getRank());
            bean.setSubj("Subject");
            bean.setMarks(dtoList.stream().mapToInt(ABCDTO::getMarks).sum());
            return bean;
        }
    )))
    .values().stream().collect(Collectors.toList());

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 03:05:17