Java Stream分组统计问题:学生总数统计错误如何修正?
修正Java Stream分组统计唯一学生数量的问题
问题场景
处理存储为Map<String, Student>的学生数据,需求如下:
- 过滤掉
State为OTHER的记录 - 按学生日期字段的月份分组
- 每个分组统计两个指标:
- 每月总价值:所有符合条件学生的
value字段求和 - 唯一学生数量:去重后的学生总数(如1月实际应统计1个,原实现错误统计为3个)
- 每月总价值:所有符合条件学生的
- 最终转换为
ResultDto列表
核心错误原因
原实现大概率直接统计了分组内的元素总数,而没有基于学生的唯一标识(如id)去重,导致重复计数。
相关类定义(参考)
// State枚举 enum State { ACTIVE, INACTIVE, OTHER } // Student类(包含唯一标识id、状态、日期、价值字段) class Student { private String id; private State state; private LocalDate enrollDate; private BigDecimal value; // 构造器、getter方法省略 } // 分组统计中间对象 class PersonGroupMetric { private BigDecimal totalValue; private long uniqueStudentCount; public PersonGroupMetric(BigDecimal totalValue, long uniqueStudentCount) { this.totalValue = totalValue; this.uniqueStudentCount = uniqueStudentCount; } // getter方法省略 } // 最终结果DTO class ResultDto { private int month; private BigDecimal totalValue; private long uniqueStudentCount; public ResultDto(int month, BigDecimal totalValue, long uniqueStudentCount) { this.month = month; this.totalValue = totalValue; this.uniqueStudentCount = uniqueStudentCount; } // getter方法省略 }
正确实现方案
方案1:Java 12+ 用Collectors.teeing(简洁版)
teeing可以同时执行两个收集操作,再合并结果:
List<ResultDto> result = studentMap.values().stream() // 过滤掉State为OTHER的学生 .filter(student -> !State.OTHER.equals(student.getState())) // 按月份分组,同时统计总价值和唯一学生数 .collect(Collectors.groupingBy( student -> student.getEnrollDate().getMonthValue(), Collectors.teeing( // 收集器1:计算总价值 Collectors.reducing(BigDecimal.ZERO, Student::getValue, BigDecimal::add), // 收集器2:收集唯一学生ID到Set,取大小得到去重后的数量 Collectors.collectingAndThen( Collectors.mapping(Student::getId, Collectors.toSet()), Set::size ), // 合并两个收集结果为PersonGroupMetric (totalVal, uniqueCount) -> new PersonGroupMetric(totalVal, uniqueCount) ) )) // 转换为ResultDto列表 .entrySet().stream() .map(entry -> new ResultDto( entry.getKey(), entry.getValue().getTotalValue(), entry.getValue().getUniqueStudentCount() )) .collect(Collectors.toList());
方案2:Java 8 兼容版(自定义收集器)
如果项目使用Java 8(不支持teeing),可以用自定义收集器实现:
// 先分组统计 Map<Integer, PersonGroupMetric> groupedMetrics = studentMap.values().stream() .filter(student -> !State.OTHER.equals(student.getState())) .collect(Collectors.groupingBy( student -> student.getEnrollDate().getMonthValue(), Collector.of( // 初始化容器:存储总价值和学生ID集合(自动去重) () -> new Object() { BigDecimal totalValue = BigDecimal.ZERO; Set<String> uniqueStudentIds = new HashSet<>(); }, // 累加逻辑:添加价值、存入学生ID (container, student) -> { container.totalValue = container.totalValue.add(student.getValue()); container.uniqueStudentIds.add(student.getId()); }, // 并行流时的容器合并逻辑 (container1, container2) -> { container1.totalValue = container1.totalValue.add(container2.totalValue); container1.uniqueStudentIds.addAll(container2.uniqueStudentIds); return container1; }, // 转换为PersonGroupMetric container -> new PersonGroupMetric(container.totalValue, container.uniqueStudentIds.size()) ) )); // 转换为ResultDto列表 List<ResultDto> resultDtoList = groupedMetrics.entrySet().stream() .map(entry -> new ResultDto( entry.getKey(), entry.getValue().getTotalValue(), entry.getValue().getUniqueStudentCount() )) .collect(Collectors.toList());
关键修正点
- 唯一学生数统计:必须通过学生的唯一标识(如
id)存入Set实现去重,再取Set的大小,而非直接统计分组内的元素数量 - 总价值统计:直接对所有过滤后的学生的
value字段求和,无需去重(若需求为每个学生只算一次价值,可先按学生ID去重再求和)
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

