Java Stream多条件分组:求和与人员去重计数问题排查
Java Stream多条件分组与唯一人员统计方案
核心需求拆解
- 从
Map<String, List<Person>>中提取所有Person数据 - 按
event过滤(仅保留STATUS1-STATUS4的记录) - 按
eventDate的月份分组 - 对每个分组统计:
- 所有
value的总和(Total Sum) - 唯一
id的人员数量(Person Count,避免重复统计同一人的多条event记录)
- 所有
解决代码实现
第一步:定义统计结果DTO
用于存储每个月份的统计数据:
class MonthStatsDto { private int month; private int totalSum; private long personCount; public MonthStatsDto(int month, int totalSum, long personCount) { this.month = month; this.totalSum = totalSum; this.personCount = personCount; } // 可选:重写toString方便结果打印 @Override public String toString() { return String.format("%-4d | %-9d | %-12d", month, totalSum, personCount); } }
第二步:Stream处理逻辑
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; // 假设你的数据源是Map<String, List<Person>> personMap Map<Integer, MonthStatsDto> monthlyStats = personMap.values() // 扁平化Map中的所有Person列表 .stream() .flatMap(List::stream) // 多条件过滤:仅保留指定event类型的记录 .filter(person -> Arrays.asList("STATUS1", "STATUS2", "STATUS3", "STATUS4").contains(person.getEvent())) // 按月份分组,并同时计算两个统计指标 .collect(Collectors.groupingBy( // 提取eventDate的月份(1-12)作为分组key person -> person.getEventDate().getMonthValue(), // 对每个分组的Person集合进行聚合 Collectors.collectingAndThen( Collectors.toList(), persons -> { // 计算value总和 int totalSum = persons.stream().mapToInt(Person::getValue).sum(); // 统计唯一人员数量:通过Set去重id后取大小 long uniquePersonCount = persons.stream() .map(Person::getId) .distinct() .count(); // 返回当前月份的统计DTO return new MonthStatsDto(persons.get(0).getEventDate().getMonthValue(), totalSum, uniquePersonCount); } ) )); // 打印结果 System.out.println("Month | Total Sum | Person Count"); monthlyStats.values().forEach(System.out::println);
关键优化点(解决重复计数问题)
之前的代码出现重复计数,是因为直接统计了分组后List<Person>的大小——同一人员可能有多条event记录,导致被多次统计。
解决核心:对人员id进行去重,通过distinct()或者Collectors.toSet()方式,确保每个id只被计数一次。
进阶优化(Java 12+)
如果使用Java 12及以上版本,可以用Collectors.teeing简化聚合逻辑,避免多次遍历分组集合:
Map<Integer, MonthStatsDto> monthlyStats = personMap.values() .stream() .flatMap(List::stream) .filter(p -> Arrays.asList("STATUS1", "STATUS2", "STATUS3", "STATUS4").contains(p.getEvent())) .collect(Collectors.groupingBy( p -> p.getEventDate().getMonthValue(), Collectors.teeing( // 第一个收集器:计算value总和 Collectors.summingInt(Person::getValue), // 第二个收集器:收集唯一id到Set Collectors.mapping(Person::getId, Collectors.toSet()), // 合并两个结果到DTO (totalSum, uniqueIds) -> { // 从分组元素中取月份(所有元素属于同一月份) int month = personMap.values().stream() .flatMap(List::stream) .filter(p -> uniqueIds.contains(p.getId())) .findFirst() .get() .getEventDate() .getMonthValue(); return new MonthStatsDto(month, totalSum, uniqueIds.size()); } ) ));
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

