如何用Java Stream按personalId汇总薪资并转换为SalaryDto?
问题描述
现有一个Map<String, List<Employee>>集合,键为Employee的id,值是对应id的Employee实例列表,示例数据如下:
Id, Name , Surname, Salary personalId1, "Name 1","Surname 1", 100 personalId2, "Name 2","Surname 2", 100 personalId3, "Name 3","Surname 3", 100 personalId1, "Name 1","Surname 1", 100 personalId2, "Name 2","Surname 2", 100 personalId2, "Name 2","Surname 2", 100 ...
需要按personalId分组,汇总每个id对应的薪资总和,最终得到如下格式的结果:
personalId1, "Name 1","Surname 1", 200 personalId2, "Name 2","Surname 2", 300 personalId3, "Name 3","Surname 3", 100
已定义SalaryDto类:
public class SalaryDto { private String id; private String name; private String surname; private BigDecimal totalSalary; // 需补充构造方法、getter/setter,示例构造方法: // public SalaryDto(String id, String name, String surname, BigDecimal totalSalary) {} // 用于累加薪资的方法: public void addSalary(BigDecimal salary) { this.totalSalary = this.totalSalary.add(salary); } }
尝试用Java Stream实现时,在flatMap后遇到问题,希望用reduce实现按personalId汇总,现有代码片段:
List<SalaryDto> totalSalary = employees.values().stream() .flatMap(List::stream) .map(e-> new SalaryDto(...)) .collect(groupingBy(Personal::getID, Collectors.toList()));
请问该如何正确实现?
解决方案
方式一:分组+reducing组合实现
直接通过Collectors.groupingBy搭配Collectors.reducing,一次性完成分组与薪资汇总,代码更简洁:
import java.util.stream.Collectors; import java.util.List; List<SalaryDto> totalSalaryList = employees.values().stream() .flatMap(List::stream) // 按personalId分组,同时对每组的Employee进行reduce合并 .collect(Collectors.groupingBy( Employee::getId, Collectors.reducing( null, emp -> new SalaryDto(emp.getId(), emp.getName(), emp.getSurname(), emp.getSalary()), (dto1, dto2) -> { if (dto1 == null) return dto2; dto1.addSalary(dto2.getTotalSalary()); return dto1; } ) )) .values() .stream() .collect(Collectors.toList());
方式二:先分组再显式reduce
如果更倾向于单独使用reduce操作,可以先按id分组,再对每组的Employee列表单独执行reduce:
import java.util.stream.Collectors; import java.util.List; import java.util.Map; // 先按id分组 Map<String, List<Employee>> groupedEmployees = employees.values().stream() .flatMap(List::stream) .collect(Collectors.groupingBy(Employee::getId)); // 对每个分组执行reduce汇总薪资 List<SalaryDto> totalSalaryList = groupedEmployees.entrySet().stream() .map(entry -> { String id = entry.getKey(); List<Employee> empList = entry.getValue(); // 初始化SalaryDto,用第一个员工的姓名,薪资初始为0 return empList.stream() .reduce( new SalaryDto(id, empList.get(0).getName(), empList.get(0).getSurname(), BigDecimal.ZERO), (dto, emp) -> { dto.addSalary(emp.getSalary()); return dto; }, (dto1, dto2) -> { dto1.addSalary(dto2.getTotalSalary()); return dto1; } ); }) .collect(Collectors.toList());
关键注意点
- 确保
SalaryDto有对应的构造方法和addSalary方法,或者直接通过setter更新薪资总和; - 若薪资类型为
BigDecimal,必须使用BigDecimal.add()进行累加,避免浮点数精度丢失; - 假设同一
personalId对应的员工姓名、姓氏一致,因此汇总时直接取组内第一个员工的姓名即可。
内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu
相关产品推荐
相关产品推荐

