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实现逻辑存在明显问题:
- 第一个循环中,每次遍历
ABCDTO都新建ResultsBean但未赋值任何字段,且直接用id作为key存入HashMap,会导致同一id的后续数据直接覆盖之前的entry,完全没保留原始数据。 - 第二个循环逻辑混乱:将
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
相关产品推荐
相关产品推荐

