题目
使用Stream API实现多级分组与聚合统计
信息
- 类型:问答
- 难度:⭐⭐
考点
Stream API, Collectors.groupingBy, Lambda表达式, 方法引用, Optional处理
快速回答
使用Java 8 Stream API实现多级分组与聚合统计的核心步骤:
- 创建数据源并转换为Stream
- 使用
Collectors.groupingBy进行多级分组 - 结合
Collectors.mapping和Collectors.averagingDouble进行聚合计算 - 使用
Optional安全处理可能为空的结果 - 通过方法引用简化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表达式,提升可读性
最佳实践
- 空值防御:始终对可能为null的字段使用
Optional包装 - 并行流慎用:数据量小时避免使用
parallelStream(),可能降低性能 - 收集器组合:通过
Collectors.collectingAndThen可对结果进行后处理 - 代码可读性:将复杂收集器拆分为局部变量,例如:
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