侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

使用Stream API实现多级分组与聚合统计

2025-12-13 / 0 评论 / 4 阅读

题目

使用Stream API实现多级分组与聚合统计

信息

  • 类型:问答
  • 难度:⭐⭐

考点

Stream API, Collectors.groupingBy, Lambda表达式, 方法引用, Optional处理

快速回答

使用Java 8 Stream API实现多级分组与聚合统计的核心步骤:

  1. 创建数据源并转换为Stream
  2. 使用Collectors.groupingBy进行多级分组
  3. 结合Collectors.mappingCollectors.averagingDouble进行聚合计算
  4. 使用Optional安全处理可能为空的结果
  5. 通过方法引用简化Lambda表达式
## 解析

场景描述

给定员工数据集合,要求:
1. 按部门(department)和职级(level)两级分组
2. 统计每组员工的平均薪资
3. 处理可能存在的空值情况

代码示例

import java.util.*;
import java.util.stream.Collectors;

class Employee {
    private String name;
    private String department;
    private String level;
    private Double salary;

    // 构造方法/getters省略
}

public class GroupingExample {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "Engineering", "Senior", 120000.0),
            new Employee("Bob", "Engineering", "Mid", 90000.0),
            new Employee("Charlie", "HR", "Junior", null),  // 包含空值
            new Employee("David", "Engineering", "Senior", 115000.0)
        );

        // 多级分组与聚合
        Map<String, Map<String, Optional<Double>>> result = employees.stream()
            .collect(Collectors.groupingBy(
                Employee::getDepartment,
                Collectors.groupingBy(
                    Employee::getLevel,
                    Collectors.mapping(
                        emp -> Optional.ofNullable(emp.getSalary()),
                        Collectors.averagingDouble(
                            optSalary -> optSalary.orElse(0.0)
                        )
                    )
                )
            ));

        // 结果输出
        result.forEach((dept, levelMap) -> {
            System.out.println("Department: " + dept);
            levelMap.forEach((level, avg) -> 
                System.out.printf("  Level %s: Average salary %.2f%n", 
                    level, avg.orElse(0.0))
            );
        });
    }
}

原理说明

  • 多级分组:通过嵌套groupingBy实现,第一级按部门分组,第二级按职级分组
  • 空值处理:使用Optional.ofNullable包装可能为null的薪资字段
  • 聚合计算Collectors.averagingDouble计算平均值,结合orElse处理空值
  • 方法引用Employee::getDepartment替代Lambda表达式,提升可读性

最佳实践

  1. 空值防御:始终对可能为null的字段使用Optional包装
  2. 并行流慎用:数据量小时避免使用parallelStream(),可能降低性能
  3. 收集器组合:通过Collectors.collectingAndThen可对结果进行后处理
  4. 代码可读性:将复杂收集器拆分为局部变量,例如:
    Collector<Employee, ?, Double> avgCollector = 
        Collectors.mapping(/*...*/, Collectors.averagingDouble(/*...*/));

常见错误

错误类型示例解决方案
忽略空值emp -> emp.getSalary()使用Optional.ofNullable包装
重复创建流对同一集合多次调用stream()复用Stream或收集结果
副作用操作在Lambda中修改外部变量使用不可变对象或reduce
嵌套过深超过3层groupingBy拆分为多个步骤或使用记录类

扩展知识

  • Java 16记录类:可用record Employee(...)简化DTO定义
  • 下游收集器
    • Collectors.summarizingDouble:一次性获取总和/平均值/最大最小值
    • Collectors.filtering(Java 9+):在收集时过滤元素
  • 性能优化:对于大型数据集,使用ConcurrentMap配合groupingByConcurrent