目录
Java Stream API 中的 sorted()
方法是一个强大的中间操作,它允许我们对流中的元素进行排序。默认情况下,sorted()
要求元素实现 Comparable
接口,但在实际应用中,我们经常需要根据特定业务规则进行自定义排序。本文将深入探讨如何使用 sorted()
方法实现自定义排序,涵盖各种常见场景和高级技巧。
一、sorted 方法基础
Java Stream 提供了两种 sorted()
方法重载:
-
自然排序:要求元素实现
Comparable
接口Stream<T> sorted()
-
自定义排序:通过
Comparator
指定排序规则Stream<T> sorted(Comparator<? super T> comparator)
二、自定义排序的基本实现
1. 使用 Lambda 表达式创建 Comparator
// 示例1:按字符串长度排序
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedByLength = words.stream()
.sorted((s1, s2) -> s1.length() - s2.length())
.toList();
System.out.println(sortedByLength); // 输出:[date, apple, cherry, banana]
// 示例2:按绝对值大小排序
List<Integer> numbers = Arrays.asList(-5, 2, -8, 1, 3);
List<Integer> 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<String> sortedByLength2 = words.stream()
.sorted(Comparator.comparing(String::length))
.toList();
// 使用 Comparator.comparingInt 优化基本类型比较
List<Integer> 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<Person> 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<Person> 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<Person> 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<String> stringsWithNulls = Arrays.asList("apple", null, "banana", null, "cherry");
// null 值排在前面
List<String> sortedWithNullsFirst = stringsWithNulls.stream()
.sorted(Comparator.nullsFirst(Comparator.naturalOrder()))
.toList();
System.out.println(sortedWithNullsFirst);
// 输出:[null, null, apple, banana, cherry]
// null 值排在后面
List<String> 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<Product> products = Arrays.asList(
new Product("Laptop", 1200.0),
new Product("Mouse", null),
new Product("Keyboard", 50.0)
);
// 按价格排序,null 价格排在最后
List<Product> sortedByPrice = products.stream()
.sorted(Comparator.comparing(Product::getPrice, Comparator.nullsLast(Double::compare)))
.toList();
五、逆序排序与自定义比较逻辑
1. 逆序排序
使用 Comparator.reverseOrder()
或 Comparator.comparing().reversed()
实现逆序:
// 字符串长度逆序排序
List<String> reversedByLength = words.stream()
.sorted(Comparator.comparing(String::length).reversed())
.toList();
// 另一种逆序写法
List<String> reversedByLength2 = words.stream()
.sorted((s1, s2) -> s2.length() - s1.length())
.toList();
2. 自定义复杂比较逻辑
对于更复杂的业务规则,可以实现 Comparator
接口:
// 示例:按字符串长度排序,长度相同则按字母顺序排序
List<String> complexSort = words.stream()
.sorted(new Comparator<String>() {
@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<String> complexSort2 = words.stream()
.sorted((s1, s2) -> {
int lengthCompare = Integer.compare(s1.length(), s2.length());
return lengthCompare != 0 ? lengthCompare : s1.compareTo(s2);
})
.toList();
六、性能优化与注意事项
1. 基本类型与装箱类型
对于基本类型(如 int
、long
、double
),优先使用 comparingInt
、comparingLong
、comparingDouble
避免装箱拆箱开销:
// 性能优化示例
List<Integer> numbers = Arrays.asList(5, 3, 8, 1, 2);
// 避免装箱
List<Integer> optimizedSort = numbers.stream()
.sorted(Comparator.comparingInt(Integer::intValue))
.toList();
2. 排序稳定性
Stream.sorted()
使用的是稳定排序算法(TimSort),即相等元素的相对顺序不会改变。这在多级排序中尤为重要:
// 示例:先按部门排序,再按工资排序
List<Employee> employees = ...;
List<Employee> sortedEmployees = employees.stream()
.sorted(Comparator.comparing(Employee::getDepartment)
.thenComparingDouble(Employee::getSalary))
.toList();
3. 并行流排序性能
在并行流中,排序操作可能会导致性能下降,因为需要全局数据重组。谨慎在并行流中使用复杂排序:
// 并行流排序示例
List<Integer> parallelSorted = numbers.parallelStream()
.sorted()
.toList();
七、实战案例
1. 电商商品排序系统
class Product {
private String name;
private double price;
private int salesVolume;
private LocalDateTime createTime;
// 构造方法、getter和setter略
}
// 按价格升序排序
List<Product> sortedByPrice = products.stream()
.sorted(Comparator.comparingDouble(Product::getPrice))
.toList();
// 按销量降序,销量相同则按创建时间降序
List<Product> 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<LogEntry> sortedLogs = logs.stream()
.sorted(Comparator.comparing(LogEntry::getTimestamp))
.toList();
// 按日志级别排序(自定义顺序:ERROR > WARN > INFO > DEBUG)
List<LogEntry> 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();
八、总结与最佳实践
-
优先使用方法引用和静态工具方法:
// 推荐写法 sorted(Comparator.comparing(Person::getAge)) // 避免冗余的 Lambda sorted((p1, p2) -> p1.getAge() - p2.getAge())
-
多级排序使用链式调用:
sorted(Comparator.comparing(Person::getDepartment) .thenComparing(Person::getAge) .thenComparing(Person::getName))
-
处理 null 值:
sorted(Comparator.nullsLast(Comparator.comparing(Person::getName)))
-
基本类型优化:
sorted(Comparator.comparingInt(Person::getAge)) // 避免装箱
-
复杂比较器提取为常量:
public static final Comparator<Person> AGE_NAME_COMPARATOR = Comparator.comparingInt(Person::getAge) .thenComparing(Person::getName); // 使用时 sorted(AGE_NAME_COMPARATOR)
通过掌握 sorted()
方法的各种用法,你可以灵活应对各种复杂的排序需求,编写出简洁、高效且易于维护的代码。在实际开发中,合理运用 Comparator
的各种工具方法和特性,能够显著提升代码质量和开发效率。