目录
3.4 分组过滤(Collectors.filtering)
一、引言
在 Java 8 引入 Stream API 后,Collectors.groupingBy作为强大的数据分组工具,极大简化了集合元素按特定条件分组的操作。然而,其能力远不止基础的单一字段分组。本文将深入剖析groupingBy的高级分组策略,包括多级分组、自定义分类器、下游收集器组合等核心技术,并通过实际案例展示其在复杂业务场景中的应用。
二、Collectors.groupingBy 基础回顾
2.1 基本用法
Collectors.groupingBy是一个终端操作,用于将流中的元素按指定条件分组,返回一个Map<K, List<T>>。其基础形式有三种重载:
// 1. 单参数:按Function分类,默认下游收集器为toList()
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// 2. 双参数:指定下游收集器(如counting())
Map<Department, Long> deptCount = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment, Collectors.counting()));
// 3. 三参数:指定Map实现类型(如TreeMap)和下游收集器
Map<Department, Set<Employee>> deptSet = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
TreeMap::new,
Collectors.toSet()
));
2.2 核心概念
- 分类函数(Classifier):决定元素分组的依据,如
Employee::getDepartment。 - 下游收集器(Downstream Collector):对每个分组内的元素进一步处理,如计数、求和、转换等。
- Map 工厂(Map Supplier):指定结果 Map 的具体实现(如
TreeMap、ConcurrentHashMap)。
三、高级分组策略实战
3.1 多级分组(嵌套 groupingBy)
通过嵌套groupingBy可实现多级分组,形成嵌套 Map 结构。
案例:按部门和职位分组员工
Map<Department, Map<Position, List<Employee>>> result = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment, // 一级分组:部门
Collectors.groupingBy(
Employee::getPosition // 二级分组:职位
)
));
// 结果访问示例
result.get(IT_DEPT).get(ENGINEER); // 获取IT部门的工程师列表
性能优化技巧
- 对大数据集,建议在内部嵌套
Collectors.mapping以减少中间集合的创建:
Map<Department, Map<Position, List<String>>> nameMap = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(
Employee::getPosition,
Collectors.mapping(Employee::getName, Collectors.toList())
)
));
3.2 自定义分类器(复杂分组逻辑)
通过自定义分类函数实现灵活分组,如范围分组、条件分组等。
案例:按薪资范围分组员工
Map<String, List<Employee>> salaryRanges = employees.stream()
.collect(Collectors.groupingBy(employee -> {
double salary = employee.getSalary();
if (salary < 5000) return "低收入";
else if (salary < 10000) return "中等收入";
else return "高收入";
}));
案例:按多条件组合分组
Map<String, List<Employee>> complexGroups = employees.stream()
.collect(Collectors.groupingBy(employee ->
(employee.isFullTime() ? "全职-" : "兼职-") +
(employee.getExperience() > 5 ? "资深" : "初级")
));
3.3 下游收集器的高级组合
结合多种下游收集器实现复杂统计需求。
1. 分组计数与排序
// 按部门统计员工数量并按人数排序
Map<Department, Long> deptSize = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
TreeMap::new, // 按部门自然顺序排序
Collectors.counting()
));
2. 分组求和与平均值
// 按部门计算平均薪资
Map<Department, Double> avgSalary = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
// 按部门计算薪资总和与最高值
Map<Department, DoubleSummaryStatistics> stats = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.summarizingDouble(Employee::getSalary)
));
// 获取IT部门的薪资统计信息
stats.get(IT_DEPT).getAverage(); // 平均薪资
stats.get(IT_DEPT).getMax(); // 最高薪资
3. 分组映射与转换
// 按部门收集员工姓名列表
Map<Department, List<String>> deptNames = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.mapping(Employee::getName, Collectors.toList())
));
// 按部门获取最高薪资员工
Map<Department, Optional<Employee>> topEmployees = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))
));
3.4 分组过滤(Collectors.filtering)
Java 9 引入的Collectors.filtering允许在分组时过滤元素。
案例:按部门分组并过滤出薪资大于 10000 的员工
Map<Department, List<Employee>> filteredDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.filtering(
e -> e.getSalary() > 10000,
Collectors.toList()
)
));
四、性能优化与最佳实践
4.1 大数据集分组优化
- 并行流与并发 Map:对海量数据,使用
Collectors.groupingByConcurrent结合并行流提升性能:
ConcurrentMap<Department, List<Employee>> concurrentResult = employees.parallelStream()
.collect(Collectors.groupingByConcurrent(Employee::getDepartment));
- 预排序数据:若数据已按分组字段排序,使用
Collectors.groupingByConcurrent的重载版本可减少哈希冲突:
Map<Department, List<Employee>> sortedResult = employees.stream()
.sorted(Comparator.comparing(Employee::getDepartment))
.collect(Collectors.groupingBy(
Employee::getDepartment,
LinkedHashMap::new, // 保持分组顺序
Collectors.toList()
));
4.2 避免内存溢出
- 对超大数据集,考虑使用
Collectors.reducing替代多级分组,减少中间 Map 的创建:
Map<Department, Integer> totalSalaries = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.reducing(
0,
Employee::getSalary,
Integer::sum
)
));
五、典型应用场景
5.1 电商订单分析
// 按商品类别分组并统计销售总额
Map<Category, Double> categorySales = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.groupingBy(
OrderItem::getCategory,
Collectors.summingDouble(item -> item.getPrice() * item.getQuantity())
));
5.2 日志数据分析
// 按小时统计API请求数
Map<Integer, Long> hourlyRequests = logs.stream()
.collect(Collectors.groupingBy(
log -> LocalDateTime.parse(log.getTimestamp()).getHour(),
Collectors.counting()
));
5.3 金融风控系统
// 按用户ID分组并检测异常交易
Map<String, List<Transaction>> suspicious = transactions.stream()
.filter(t -> t.getAmount() > 10000 && !t.isVerified())
.collect(Collectors.groupingBy(Transaction::getUserId));
六、常见问题与解决方案
6.1 空指针异常处理
当分类函数返回null时,会抛出NullPointerException。可通过包装分类函数避免:
Map<Department, List<Employee>> safeGrouping = employees.stream()
.collect(Collectors.groupingBy(
e -> Optional.ofNullable(e.getDepartment()).orElse(UNKNOWN_DEPT)
));
6.2 保持分组顺序
若需保持元素在原流中的顺序,使用LinkedHashMap作为 Map 工厂:
Map<Department, List<Employee>> orderedGroups = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
LinkedHashMap::new,
Collectors.toList()
));
6.3 自定义 Map 值类型
默认分组结果为List<T>,可通过下游收集器自定义:
// 按部门分组并转换为员工ID集合
Map<Department, Set<Long>> deptIds = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.mapping(Employee::getId, Collectors.toSet())
));
七、总结
Collectors.groupingBy的高级分组策略为复杂数据处理提供了强大支持,通过多级分组、自定义分类器、下游收集器组合等技术,可高效解决各类业务场景中的数据聚合需求。在实际应用中,需根据数据规模和业务逻辑选择合适的分组策略,并注意性能优化和边界条件处理。掌握这些高级技巧,将显著提升 Java 数据处理的效率和代码质量。
4358

被折叠的 条评论
为什么被折叠?



