目录
在 Java 的 Stream API 中,sorted
方法是实现集合元素排序的核心操作。默认排序基于元素的自然顺序,但实际开发中常需根据业务规则定制排序逻辑。本文将从基础用法到高级技巧,全面解析如何通过Comparator
实现灵活的自定义排序。
一、sorted 方法的两种形式
Java Stream 提供了两种sorted
方法重载,对应不同的排序策略:
-
无参 sorted ()
要求元素实现Comparable
接口,按自然顺序排序:List<Integer> nums = Arrays.asList(3, 1, 4, 2); nums.stream().sorted().forEach(System.out::print); // 输出:1234
-
带 Comparator 的 sorted (Comparator)
自定义排序规则,灵活性更高:List<String> words = Arrays.asList("banana", "apple", "cherry"); words.stream() .sorted(Comparator.comparingInt(String::length)) .forEach(System.out::println); // 按长度排序
二、基础自定义排序实现
1. 使用 Lambda 表达式创建 Comparator
// 示例1:按字符串长度降序排序
List<String> sortedByLength = Arrays.asList("dog", "elephant", "cat")
.stream()
.sorted((s1, s2) -> s2.length() - s1.length())
.toList(); // 输出:[elephant, dog, cat]
// 示例2:按绝对值升序排序
List<Integer> sortedByAbs = Arrays.asList(-5, 2, -8, 1)
.stream()
.sorted((n1, n2) -> Math.abs(n1) - Math.abs(n2))
.toList(); // 输出:[1, 2, -5, -8]
2. 利用 Comparator 静态方法简化代码
Java 8 为Comparator
提供了丰富的静态工厂方法:
// 按字符串长度排序(推荐写法)
List<String> optimizedSort = words.stream()
.sorted(Comparator.comparing(String::length))
.toList();
// 逆序排序
List<String> reversed = words.stream()
.sorted(Comparator.comparing(String::length).reversed())
.toList(); // 长度降序
三、对象属性排序与复合条件排序
1. 自定义对象按单属性排序
class Person {
private String name;
private int age;
// 构造器与getter略
@Override public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
List<Person> people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 20),
new Person("Charlie", 30)
);
// 按年龄升序排序
List<Person> sortedByAge = people.stream()
.sorted(Comparator.comparingInt(Person::getAge))
.toList(); // 输出:Bob, Alice, Charlie
2. 多条件复合排序(thenComparing)
// 先按年龄升序,年龄相同则按名字字母顺序降序
List<Person> complexSort = people.stream()
.sorted(Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName, Comparator.reverseOrder()))
.toList();
四、null 值安全排序与特殊场景处理
1. 处理元素为 null 的集合
List<String> strings = Arrays.asList("apple", null, "banana", null, "cherry");
// null元素排在最前
List<String> nullsFirst = strings.stream()
.sorted(Comparator.nullsFirst(Comparator.naturalOrder()))
.toList(); // 输出:[null, null, apple, banana, cherry]
// null元素排在最后
List<String> nullsLast = strings.stream()
.sorted(Comparator.nullsLast(Comparator.reverseOrder()))
.toList(); // 输出:[cherry, banana, apple, null, null]
2. 对象属性为 null 的排序
class Product {
private String name;
private Double price; // 价格可能为null
// 构造器与getter略
}
List<Product> products = Arrays.asList(
new Product("Laptop", 1000.0),
new Product("Headphone", null),
new Product("Mouse", 50.0)
);
// 按价格排序,null价格排在最后
List<Product> sortedByPrice = products.stream()
.sorted(Comparator.comparing(Product::getPrice,
Comparator.nullsLast(Double::compare)))
.toList();
五、高级排序技巧与性能优化
1. 自定义比较逻辑(Comparator 实现类)
// 复杂比较器:先按长度,再按首字母ASCII码
class CustomComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
int lenComp = Integer.compare(s1.length(), s2.length());
return lenComp != 0 ? lenComp : s1.charAt(0) - s2.charAt(0);
}
}
List<String> result = words.stream()
.sorted(new CustomComparator())
.toList();
2. 基本类型排序优化(避免装箱)
List<Integer> numbers = Arrays.asList(5, 3, 8, 1);
// 推荐:使用comparingInt避免Integer装箱
List<Integer> optimized = numbers.stream()
.sorted(Comparator.comparingInt(Integer::intValue))
.toList();
// 低效:自动装箱会产生额外开销
List<Integer> inefficient = numbers.stream()
.sorted((a, b) -> a - b)
.toList();
3. 并行流中的排序注意事项
并行流排序会触发全局数据重组,可能影响性能:
// 谨慎使用并行排序
List<Integer> parallelResult = numbers.parallelStream()
.sorted()
.toList();
六、实战场景:复杂业务排序案例
1. 电商商品多维排序
class Product {
private String name;
private double price;
private int sales;
private LocalDateTime createTime;
// 构造器与getter略
}
// 排序规则:
// 1. 销量降序(优先)
// 2. 价格升序
// 3. 新品优先(创建时间近)
List<Product> sortedProducts = products.stream()
.sorted(Comparator.comparingInt(Product::getSales).reversed()
.thenComparingDouble(Product::getPrice)
.thenComparing(Product::getCreateTime, Comparator.reverseOrder()))
.toList();
2. 日志条目按优先级和时间排序
enum LogLevel { DEBUG, INFO, WARN, ERROR }
class LogEntry {
private LogLevel level;
private LocalDateTime timestamp;
// 构造器与getter略
}
// 自定义日志级别顺序:ERROR > WARN > INFO > DEBUG
List<LogEntry> logs = ...;
List<LogEntry> sortedLogs = 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())
.thenComparing(LogEntry::getTimestamp))
.toList();
七、排序核心原理与最佳实践
-
排序稳定性
Java 的sorted
采用 TimSort 算法,保证稳定排序(相等元素相对顺序不变),适合多级排序场景。 -
Comparator 链最佳实践
- 用
Comparator.comparing
代替手动a-b
计算 - 复杂比较器提取为静态常量
- 基本类型优先使用
comparingInt/Long/Double
- 用
-
性能优化关键点
- 避免在并行流中进行复杂排序
- 减少不必要的对象装箱(如
int
vsInteger
) - 对大型集合考虑先排序再处理
// 推荐写法:
static final Comparator<Person> AGE_NAME_COMPARATOR =
Comparator.comparingInt(Person::getAge)
.thenComparing(Person::getName);
// 使用时直接引用常量
people.stream().sorted(AGE_NAME_COMPARATOR).forEach(...)
通过灵活运用Comparator
的各种特性,Java Stream 的sorted
方法能满足几乎所有排序需求。从简单的属性排序到复杂的业务规则,合理组合comparing
、thenComparing
、nullsFirst/Last
等方法,可编写出高效且易维护的排序代码。在实际开发中,建议将常用比较器封装为工具类,以提升代码复用性。