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

Java Stream多条件分组:求和与人员去重计数问题排查

Java Stream多条件分组与唯一人员统计方案

核心需求拆解

  1. 从Map<String, List<Person>>中提取所有Person数据
  2. 按event过滤(仅保留STATUS1-STATUS4的记录)
  3. 按eventDate的月份分组
  4. 对每个分组统计:
    • 所有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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 04:55:24