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

Java Stream分组统计问题:学生总数统计错误如何修正?

修正Java Stream分组统计唯一学生数量的问题

问题场景

处理存储为Map<String, Student>的学生数据,需求如下:

  • 过滤掉State为OTHER的记录
  • 按学生日期字段的月份分组
  • 每个分组统计两个指标:
    1. 每月总价值:所有符合条件学生的value字段求和
    2. 唯一学生数量:去重后的学生总数(如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());

关键修正点

  1. 唯一学生数统计:必须通过学生的唯一标识(如id)存入Set实现去重,再取Set的大小,而非直接统计分组内的元素数量
  2. 总价值统计:直接对所有过滤后的学生的value字段求和,无需去重(若需求为每个学生只算一次价值,可先按学生ID去重再求和)

内容的提问来源于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 08:32:16