用groupingBy配合maxBy可一行实现按部门分组并取每组薪资最高员工,返回Map<String, Optional<Employee>>;若需去除Optional,可用collectingAndThen包装Optional::get;处理并列最高需手动筛选。

用 groupingBy 配合 maxBy 可以一行代码完成“按部门分组,取每组薪资最高员工”的需求,关键是把 maxBy 作为下游收集器(downstream collector)传入。
基础写法:直接嵌套 maxBy
假设员工类 Employee 有 department(部门名)和 salary(薪资)字段:
Map<String, Optional<Employee>> deptToTopEmployee = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))
));
注意返回的是 Map<String, Optional<Employee>>,因为 maxBy 返回 Optional(空集合时不会抛异常,而是存 Optional.empty())。
去掉 Optional:用 collectingAndThen 包装
如果确定每个部门至少有一人,想直接得到 Map<String, Employee>,可用 collectingAndThen 解包:
立即学习“Java免费学习笔记(深入)”;
- 先用
maxBy找出最大值(返回Optional) - 再用
collectingAndThen对结果调用Optional::get
Map<String, Employee> deptToTopEmployee = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary)),
Optional::get
)
));
⚠️ 注意:若某部门无员工,Optional::get 会抛 NoSuchElementException。生产环境建议保留 Optional 或加判空逻辑。
处理并列最高:用 filtering + maxBy 或 toList 后筛选
maxBy 只返回一个(遇到相等时取第一个),如需保留所有并列最高者,不能直接用它:
- 方案一:先用
groupingBy(dept, mapping(salary, max))拿出最高薪资,再二次过滤 - 方案二(更简洁):用
toCollection(TreeSet::new)或自定义收集器 - 常用做法是先分组,再对每组手动找所有等于最大值的员工
例如:
Map<String, List<Employee>> deptToTopEmployees = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> {
if (list.isEmpty()) return Collections.emptyList();
double maxSalary = list.stream()
.mapToDouble(Employee::getSalary)
.max()
.orElse(0.0);
return list.stream()
.filter(e -> e.getSalary() == maxSalary)
.collect(Collectors.toList());
}
)
));
性能与可读性提醒
maxBy 是终端操作,内部做单次遍历比较,时间复杂度 O(n);而上面手动找并列的写法对每组又扫一遍,小数据无感,大数据量时可考虑预计算最大值或改用 TreeSet 排序后取首尾。
实际开发中,优先用 groupingBy + maxBy 满足单个代表需求;并列场景较少,建议按业务权衡——多数时候“任选一个最高”已够用。


















