在Java业务开发中,经常会遇到嵌套集合的场景,比如一个部门对象包含多个员工列表,每个员工又有多个技能项,需要将这些多层嵌套的数据提取出来,构建成包含部门名、员工名、技能名的组合对象。使用传统循环嵌套的方式代码冗长,而Java Stream API的flatMap方法可以优雅地解决这个问题。

核心方法说明
要完成嵌套集合的扁平化和组合对象构建,主要涉及两个Stream核心方法:
- flatMap:将流中的每个元素转换为另一个流,然后将这些流合并成一个统一的流,实现集合的扁平化效果,解决多层嵌套的结构问题。
- map:对流中的每个元素进行转换操作,在这里用于根据扁平化后的原始数据构建目标组合对象。
示例场景与数据模型
假设我们有如下三层嵌套的数据结构:部门包含多个员工,员工包含多个技能,最终需要构建部门、员工、技能三者的组合对象。首先定义对应的实体类:
// 技能实体
class Skill {
private String skillName;
public Skill(String skillName) {
this.skillName = skillName;
}
public String getSkillName() {
return skillName;
}
}
// 员工实体
class Employee {
private String employeeName;
private List<Skill> skills;
public Employee(String employeeName, List<Skill> skills) {
this.employeeName = employeeName;
this.skills = skills;
}
public String getEmployeeName() {
return employeeName;
}
public List<Skill> getSkills() {
return skills;
}
}
// 部门实体
class Department {
private String departmentName;
private List<Employee> employees;
public Department(String departmentName, List<Employee> employees) {
this.departmentName = departmentName;
this.employees = employees;
}
public String getDepartmentName() {
return departmentName;
}
public List<Employee> getEmployees() {
return employees;
}
}
// 目标组合对象
class DepartmentEmployeeSkillCombo {
private String departmentName;
private String employeeName;
private String skillName;
public DepartmentEmployeeSkillCombo(String departmentName, String employeeName, String skillName) {
this.departmentName = departmentName;
this.employeeName = employeeName;
this.skillName = skillName;
}
@Override
public String toString() {
return "DepartmentEmployeeSkillCombo{" +
"departmentName='" + departmentName + ''' +
", employeeName='" + employeeName + ''' +
", skillName='" + skillName + ''' +
'}';
}
}
完整实现流程
接下来构建测试数据,然后使用Stream API完成扁平化和组合对象构建:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamFlatMapDemo {
public static void main(String[] args) {
// 构建测试数据
List<Department> departments = new ArrayList<>();
// 第一个部门
List<Skill> skillList1 = new ArrayList<>();
skillList1.add(new Skill("Java"));
skillList1.add(new Skill("MySQL"));
Employee emp1 = new Employee("张三", skillList1);
List<Skill> skillList2 = new ArrayList<>();
skillList2.add(new Skill("Python"));
Employee emp2 = new Employee("李四", skillList2);
List<Employee> empList1 = new ArrayList<>();
empList1.add(emp1);
empList1.add(emp2);
departments.add(new Department("技术部", empList1));
// 第二个部门
List<Skill> skillList3 = new ArrayList<>();
skillList3.add(new Skill("PS"));
Employee emp3 = new Employee("王五", skillList3);
List<Employee> empList2 = new ArrayList<>();
empList2.add(emp3);
departments.add(new Department("设计部", empList2));
// 使用Stream API扁平化嵌套集合并构建组合对象
List<DepartmentEmployeeSkillCombo> result = departments.stream()
// 第一层扁平化:将部门流转换为员工流,同时保留部门名信息
.flatMap(department -> department.getEmployees().stream()
.map(employee -> new Object[]{
department.getDepartmentName(),
employee.getEmployeeName(),
employee.getSkills()
}))
// 第二层扁平化:将员工对应的技能列表流展开,同时保留部门名和员工名信息
.flatMap(arr -> {
String deptName = (String) arr[0];
String empName = (String) arr[1];
List<Skill> skills = (List<Skill>) arr[2];
return skills.stream()
.map(skill -> new Object[]{deptName, empName, skill.getSkillName()});
})
// 构建目标组合对象
.map(arr -> new DepartmentEmployeeSkillCombo(
(String) arr[0],
(String) arr[1],
(String) arr[2]
))
.collect(Collectors.toList());
// 输出结果
result.forEach(System.out::println);
}
}
代码逻辑解析
上述代码的执行流程可以分为三步:
- 首先遍历部门集合,通过第一个
flatMap将每个部门的员工列表展开成员工流,同时把部门名和员工名、员工的技能列表打包成临时数组,避免后续丢失上层数据。 - 然后通过第二个
flatMap将每个员工对应的技能列表展开成技能流,同时把之前保留的部门名、员工名和当前技能名打包成新的临时数组。 - 最后通过
map方法将临时数组中的数据映射到DepartmentEmployeeSkillCombo组合对象中,再收集成列表返回。
优化写法
如果觉得临时数组的可读性较差,可以定义一个中间传输对象来替代数组,让逻辑更清晰:
// 中间传输对象
class EmpSkillTemp {
private String departmentName;
private String employeeName;
private Skill skill;
public EmpSkillTemp(String departmentName, String employeeName, Skill skill) {
this.departmentName = departmentName;
this.employeeName = employeeName;
this.skill = skill;
}
public String getDepartmentName() {
return departmentName;
}
public String getEmployeeName() {
return employeeName;
}
public Skill getSkill() {
return skill;
}
}
// 优化后的Stream处理代码
List<DepartmentEmployeeSkillCombo> optimizedResult = departments.stream()
.flatMap(department -> department.getEmployees().stream()
.flatMap(employee -> employee.getSkills().stream()
.map(skill -> new EmpSkillTemp(
department.getDepartmentName(),
employee.getEmployeeName(),
skill
))))
.map(temp -> new DepartmentEmployeeSkillCombo(
temp.getDepartmentName(),
temp.getEmployeeName(),
temp.getSkill().getSkillName()
))
.collect(Collectors.toList());
这种写法通过嵌套flatMap直接完成两层扁平化,中间用专门的传输对象承载数据,避免了数组的类型转换,可读性和维护性都更好。
注意事项
- 如果嵌套集合中可能存在空值,需要在流处理中加入
filter过滤空元素,或者使用Optional处理,避免出现空指针异常。 flatMap中返回的是流对象,不要直接返回集合,否则会报错。- 如果嵌套层级更多,只需要按照同样的思路逐层使用
flatMap展开即可,逻辑和两层嵌套的处理方式一致。
Java_Stream_APIflatMap嵌套集合组合对象修改时间:2026-06-11 03:15:38