1. 概述

本教程将探讨如何使用Streams API对存储在Map中的List元素进行排序。过程中,我们会将其与传统的List#sort(Comparator)方法进行对比,分析哪种方式更高效。

2. 问题场景

先明确问题再动手解决

假设有一个Employee类:

public class Employee {
    private String name;
    private int salary;
    private String department;
    private String sex;

    public Employee(String name, int salary, String department, String sex) {
        this.name = name;
        this.salary = salary;
        this.department = department;
        this.sex = sex;
    }
    
    //getter and setters ..
}

Employee类包含namesalarydepartmentsex字段,并提供构造方法。

我们将从CSV文件emp_not_sorted.csv中读取员工数据创建Employee对象列表:

Sales,John Doe,48000,M
HR,Jane Smith,60000,F
IT,Robert Brown,75000,M
Marketing,Alice Johnson,55000,F
Sales,Chris Green,48000,M
HR,Emily White,62000,F
IT,Michael Black,72000,M
Marketing,Linda Blue,60000,F
更多记录...

CSV文件包含departmentnamesalarysex四列。

读取CSV文件并存储到Map中:

static void populateMap(String filePath) throws IOException {
    String[] lines = readLinesFromFile(filePath);
    Arrays.asList(lines)
      .forEach(e -> {
        String[] strArr = e.split(",");
            Employee emp = new Employee(strArr[1], Integer.valueOf(strArr[2]), strArr[0], strArr[3]);

            MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.computeIfAbsent(emp.getDepartment(),
              k -> new HashMap<>())
                .computeIfAbsent(emp.getSex(), k -> new ArrayList<>())
                .add(emp);
        });
}

关键点MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEESMap<String, Map<String, List>>类型。外层Map的key是department,内层Map的key是sex

下一步,我们将访问内层Map中的Employee列表,并按salaryname排序。期望的排序结果:

Sales,Chris Green,48000,M
Sales,John Doe,48000,M
Sales,Matthew Cyan,48000,M
Sales,David Grey,50000,M
Sales,James Purple,50000,M
Sales,Aiden White,55000,M
更多记录..
HR,Isabella Magenta,60000,F
HR,Jane Smith,60000,F
HR,Emily White,62000,F
HR,Sophia Red,62000,F
更多记录..

规则:先按salary升序,salary相同时按name升序。其他部门同理。

3. 非Stream API解决方案

传统方案*直接使用List#sort(Comparator)*方法**:

void givenHashMapContainingEmployeeList_whenSortWithoutStreamAPI_thenSort() throws IOException {
    final List<Employee> lstOfEmployees = new ArrayList<>();
    MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.forEach((dept, deptToSexToEmps) ->
        deptToSexToEmps.forEach((sex, emps) ->
        {
            emps.sort(Comparator.comparingInt(Employee::getSalary).thenComparing(Employee::getName));
            emps.forEach(this::processFurther);
            lstOfEmployees.addAll(emps);
        })
    );
    String[] expectedArray = readLinesFromFile(getFilePath("emp_sorted.csv"));
    String[] actualArray = getCSVDelimitedLines(lstOfEmployees);
    assertArrayEquals(expectedArray, actualArray);
}

✅ 使用forEach()替代传统的for/while循环,这是Java 8引入的函数式编程特性。

✅ 通过Comparator#comparingInt()salary排序,再链式调用thenComparing()name排序,代码声明性强。

严重缺陷sort()方法直接修改原始Listemps变量),破坏了不可变性原则。这种突变操作:

  • 增加调试和排查问题的复杂度
  • 无法返回新集合用于后续处理
  • 需要额外循环处理元素,破坏代码流

4. Stream API解决方案

针对传统方案的缺陷,使用Stream API优雅解决

void givenHashMapContainingEmployeeList_whenSortWithStreamAPI_thenSort() throws IOException {
    final List<Employee> lstOfEmployees = new ArrayList<>();
    MAP_OF_DEPT_TO_MAP_OF_SEX_TO_EMPLOYEES.forEach((dept, deptToSexToEmps) ->
      deptToSexToEmps.forEach((sex, emps) ->
        {
             List<Employee> employees = emps.stream()
               .sorted(Comparator.comparingInt(Employee::getSalary).thenComparing(Employee::getName))
               .map(this::processFurther)
               .collect(Collectors.toList());
             lstOfEmployees.addAll(employees);
        })
    );
    String[] expectedArray = readLinesFromFile(getFilePath("emp_sorted.csv"));
    String[] actualArray = getCSVDelimitedLines(lstOfEmployees);
    assertArrayEquals(expectedArray, actualArray);
}

核心优势Stream#sorted(Comparator)返回新Stream对象,不修改原始数据

操作链优势

  1. 排序后可直接通过*map()处理每个元素(如调用processFurther()*)
  2. 支持多个中间操作(如filter/map等)
  3. 终止操作(*collect()*)生成最终结果

⚠️ 注意事项:Stream API需遵循函数式编程原则:

  • 不可变性(Immutable)
  • 无状态(Stateless)
  • 纯函数(Pure Function) 否则可能产生意外结果。

5. 总结

本文对比了*Stream#sorted(Comparator)List#sort(Comparator)*两种排序方案:

对比维度 List#sort() Stream#sorted()
原始数据修改 ✅ 直接修改 ❌ 保持不变
后续处理流畅度 ❌ 需二次循环 ✅ 链式操作
函数式编程支持 ❌ 不支持 ✅ 完全支持
代码可读性 ⚠️ 一般 ✅ 更清晰

结论:*Stream#sorted()*在代码连续性和可读性上明显优于传统方案。但使用Stream API时,务必遵循函数式编程原则,避免踩坑。

本文代码已发布在GitHub仓库


原始标题:How to Sort Map Value List by Element Field Using Java Streams | Baeldung