> 技术文档 > Java Stream 的 sorted 方法:自定义排序全解析_java stream 排序

Java Stream 的 sorted 方法:自定义排序全解析_java stream 排序

目录

一、sorted 方法基础

二、自定义排序的基本实现

1. 使用 Lambda 表达式创建 Comparator

2. 使用 Comparator 静态方法简化代码

三、处理复杂对象排序

1. 对自定义对象按属性排序

2. 多条件排序(复合排序)

四、处理空值与 null 安全排序

1. 空值处理策略

2. 对象属性可能为 null 的情况

五、逆序排序与自定义比较逻辑

1. 逆序排序

2. 自定义复杂比较逻辑

六、性能优化与注意事项

1. 基本类型与装箱类型

2. 排序稳定性

3. 并行流排序性能

七、实战案例

1. 电商商品排序系统

2. 日志时间戳排序

八、总结与最佳实践


Java Stream API 中的 sorted() 方法是一个强大的中间操作,它允许我们对流中的元素进行排序。默认情况下,sorted() 要求元素实现 Comparable 接口,但在实际应用中,我们经常需要根据特定业务规则进行自定义排序。本文将深入探讨如何使用 sorted() 方法实现自定义排序,涵盖各种常见场景和高级技巧。

一、sorted 方法基础

Java Stream 提供了两种 sorted() 方法重载:

  1. 自然排序:要求元素实现 Comparable 接口

    Stream sorted()
  2. 自定义排序:通过 Comparator 指定排序规则

    Stream sorted(Comparator comparator)
二、自定义排序的基本实现
1. 使用 Lambda 表达式创建 Comparator
// 示例1:按字符串长度排序List words = Arrays.asList(\"apple\", \"banana\", \"cherry\", \"date\");List sortedByLength = words.stream() .sorted((s1, s2) -> s1.length() - s2.length()) .toList();System.out.println(sortedByLength); // 输出:[date, apple, cherry, banana]// 示例2:按绝对值大小排序List numbers = Arrays.asList(-5, 2, -8, 1, 3);List sortedByAbs = numbers.stream() .sorted((n1, n2) -> Math.abs(n1) - Math.abs(n2)) .toList();System.out.println(sortedByAbs); // 输出:[1, 2, 3, -5, -8]
2. 使用 Comparator 静态方法简化代码

Java 8 为 Comparator 接口提供了许多实用的静态方法,使排序代码更加简洁:

// 使用 Comparator.comparing 方法List sortedByLength2 = words.stream() .sorted(Comparator.comparing(String::length)) .toList();// 使用 Comparator.comparingInt 优化基本类型比较List sortedByAbs2 = numbers.stream() .sorted(Comparator.comparingInt(Math::abs)) .toList();
三、处理复杂对象排序
1. 对自定义对象按属性排序
class Person { private String name; private int age; private LocalDate birthDate; // 构造方法、getter和setter略 @Override public String toString() { return \"Person{name=\'\" + name + \"\', age=\" + age + \", birthDate=\" + birthDate + \"}\"; }}// 按年龄升序排序List people = Arrays.asList( new Person(\"Alice\", 25, LocalDate.of(2000, 1, 1)), new Person(\"Bob\", 20, LocalDate.of(2005, 5, 5)), new Person(\"Charlie\", 30, LocalDate.of(1995, 10, 10)));List sortedByAge = people.stream() .sorted(Comparator.comparingInt(Person::getAge)) .toList();System.out.println(sortedByAge);// 输出:[Person{name=\'Bob\', age=20}, Person{name=\'Alice\', age=25}, Person{name=\'Charlie\', age=30}]
2. 多条件排序(复合排序)

使用 thenComparing() 方法可以实现多级排序:

// 先按年龄升序,年龄相同则按出生日期降序List sortedByAgeAndBirthDate = people.stream() .sorted(Comparator.comparingInt(Person::getAge) .thenComparing(Person::getBirthDate, Comparator.reverseOrder())) .toList();System.out.println(sortedByAgeAndBirthDate);
四、处理空值与 null 安全排序
1. 空值处理策略

在实际应用中,集合元素或元素属性可能为 null,直接排序会导致 NullPointerException。我们可以使用 Comparator.nullsFirst() 或 Comparator.nullsLast() 来安全处理 null 值:

// 示例:处理可能为 null 的字符串List stringsWithNulls = Arrays.asList(\"apple\", null, \"banana\", null, \"cherry\");// null 值排在前面List sortedWithNullsFirst = stringsWithNulls.stream() .sorted(Comparator.nullsFirst(Comparator.naturalOrder())) .toList();System.out.println(sortedWithNullsFirst); // 输出:[null, null, apple, banana, cherry]// null 值排在后面List sortedWithNullsLast = stringsWithNulls.stream() .sorted(Comparator.nullsLast(Comparator.naturalOrder())) .toList();System.out.println(sortedWithNullsLast); // 输出:[apple, banana, cherry, null, null]
2. 对象属性可能为 null 的情况
class Product { private String name; private Double price; // 价格可能为 null // 构造方法、getter和setter略}List products = Arrays.asList( new Product(\"Laptop\", 1200.0), new Product(\"Mouse\", null), new Product(\"Keyboard\", 50.0));// 按价格排序,null 价格排在最后List sortedByPrice = products.stream() .sorted(Comparator.comparing(Product::getPrice, Comparator.nullsLast(Double::compare))) .toList();
五、逆序排序与自定义比较逻辑
1. 逆序排序

使用 Comparator.reverseOrder() 或 Comparator.comparing().reversed() 实现逆序:

// 字符串长度逆序排序List reversedByLength = words.stream() .sorted(Comparator.comparing(String::length).reversed()) .toList();// 另一种逆序写法List reversedByLength2 = words.stream() .sorted((s1, s2) -> s2.length() - s1.length()) .toList();
2. 自定义复杂比较逻辑

对于更复杂的业务规则,可以实现 Comparator 接口:

// 示例:按字符串长度排序,长度相同则按字母顺序排序List complexSort = words.stream() .sorted(new Comparator() { @Override public int compare(String s1, String s2) { int lengthCompare = Integer.compare(s1.length(), s2.length()); if (lengthCompare != 0) {  return lengthCompare; } return s1.compareTo(s2); } }) .toList();// 使用 Lambda 简化List complexSort2 = words.stream() .sorted((s1, s2) -> { int lengthCompare = Integer.compare(s1.length(), s2.length()); return lengthCompare != 0 ? lengthCompare : s1.compareTo(s2); }) .toList();
六、性能优化与注意事项
1. 基本类型与装箱类型

对于基本类型(如 intlongdouble),优先使用 comparingIntcomparingLongcomparingDouble 避免装箱拆箱开销:

// 性能优化示例List numbers = Arrays.asList(5, 3, 8, 1, 2);// 避免装箱List optimizedSort = numbers.stream() .sorted(Comparator.comparingInt(Integer::intValue)) .toList();
2. 排序稳定性

Stream.sorted() 使用的是稳定排序算法(TimSort),即相等元素的相对顺序不会改变。这在多级排序中尤为重要:

// 示例:先按部门排序,再按工资排序List employees = ...;List sortedEmployees = employees.stream() .sorted(Comparator.comparing(Employee::getDepartment) .thenComparingDouble(Employee::getSalary)) .toList();
3. 并行流排序性能

在并行流中,排序操作可能会导致性能下降,因为需要全局数据重组。谨慎在并行流中使用复杂排序:

// 并行流排序示例List parallelSorted = numbers.parallelStream() .sorted() .toList();
七、实战案例
1. 电商商品排序系统
class Product { private String name; private double price; private int salesVolume; private LocalDateTime createTime; // 构造方法、getter和setter略}// 按价格升序排序List sortedByPrice = products.stream() .sorted(Comparator.comparingDouble(Product::getPrice)) .toList();// 按销量降序,销量相同则按创建时间降序List sortedBySalesAndTime = products.stream() .sorted(Comparator.comparingInt(Product::getSalesVolume).reversed() .thenComparing(Product::getCreateTime, Comparator.reverseOrder())) .toList();
2. 日志时间戳排序
class LogEntry { private LocalDateTime timestamp; private String message; private LogLevel level; // 构造方法、getter和setter略}// 按时间戳排序List sortedLogs = logs.stream() .sorted(Comparator.comparing(LogEntry::getTimestamp)) .toList();// 按日志级别排序(自定义顺序:ERROR > WARN > INFO > DEBUG)List sortedByLevel = logs.stream() .sorted(Comparator.comparing(LogEntry::getLevel,  Comparator.comparingInt(level -> {  switch (level) { case ERROR: return 4; case WARN: return 3; case INFO: return 2; case DEBUG: return 1; default: return 0;  } }).reversed())) .toList();
八、总结与最佳实践
  1. 优先使用方法引用和静态工具方法

    // 推荐写法sorted(Comparator.comparing(Person::getAge))// 避免冗余的 Lambdasorted((p1, p2) -> p1.getAge() - p2.getAge())
  2. 多级排序使用链式调用

    sorted(Comparator.comparing(Person::getDepartment) .thenComparing(Person::getAge) .thenComparing(Person::getName))
  3. 处理 null 值

    sorted(Comparator.nullsLast(Comparator.comparing(Person::getName)))
  4. 基本类型优化

    sorted(Comparator.comparingInt(Person::getAge)) // 避免装箱
  5. 复杂比较器提取为常量

    public static final Comparator AGE_NAME_COMPARATOR = Comparator.comparingInt(Person::getAge) .thenComparing(Person::getName);// 使用时sorted(AGE_NAME_COMPARATOR)

通过掌握 sorted() 方法的各种用法,你可以灵活应对各种复杂的排序需求,编写出简洁、高效且易于维护的代码。在实际开发中,合理运用 Comparator 的各种工具方法和特性,能够显著提升代码质量和开发效率。