为什么需要 Comparator?
我们来看一个简单的例子,理解为什么需要它。

假设我们有一个 Student 类,我们想根据学生的年龄进行排序。
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
如果我们想对 Student 对象列表进行排序,直接调用 Collections.sort() 或 List.sort() 会怎么样?
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
// 这行代码会编译失败!
// Collections.sort(students);
}
}
编译失败! 错误信息大概是:
java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
这是因为 Collections.sort() 方法需要一个前提:列表中的元素必须实现了 Comparable 接口。Comparable 接口定义了对象之间的“自然排序”(natural ordering)规则。

Comparable vs Comparator 的核心区别:
| 特性 | Comparable |
Comparator |
|---|---|---|
| 定义位置 | 在被排序的类内部实现。 | 在类外部定义一个独立的比较器。 |
| 目的 | 定义类的“自然排序”规则。String 类按字典序排序。 |
定义一个临时的、自定义的排序规则。 |
| 方法 | int compareTo(T o) |
int compare(T o1, T o2) |
| 灵活性 | 一个类只能有一个 compareTo 方法,灵活性差。 |
可以创建任意多个不同的 Comparator,非常灵活。 |
| 使用场景 | 当对象有“天然”的、公认的排序方式时(如数字大小、日期先后)。 | 当需要多种排序方式,或者无法/不想修改类源码时。 |
如何使用 Comparator
Comparator 是一个函数式接口(从 Java 8 开始),位于 java.util 包中,它提供了一种非常灵活的方式来定义排序规则。
使用匿名内部类(传统方式)
这是在 Java 8 之前最常见的方式。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
// 1. 按年龄升序排序
Comparator<Student> ageComparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// s1 的年龄小于 s2 的年龄,返回负数(s1 排在 s2 前面)
// s1 的年龄大于 s2 的年龄,返回正数(s1 排在 s2 后面)
// 如果相等,返回 0
return Integer.compare(s1.getAge(), s2.getAge());
// 也可以直接写: return s1.getAge() - s2.getAge();
}
};
Collections.sort(students, ageComparator);
System.out.println("按年龄升序排序:");
students.forEach(System.out::println);
// 2. 按年龄降序排序
Comparator<Student> ageDescComparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 只需颠倒比较顺序即可
return Integer.compare(s2.getAge(), s1.getAge());
// 或者: return s2.getAge() - s1.getAge();
}
};
Collections.sort(students, ageDescComparator);
System.out.println("\n按年龄降序排序:");
students.forEach(System.out::println);
}
}
使用 Lambda 表达式(Java 8+ 推荐)
由于 Comparator 是一个函数式接口,我们可以用更简洁的 Lambda 表达式来替代匿名内部类。

compare(T o1, T o2) 方法可以看作是一个接受两个参数、返回一个整数的函数。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
// 1. 按年龄升序排序
// (s1, s2) -> s1.getAge() - s2.getAge() 是一个 Lambda 表达式
students.sort((s1, s2) -> s1.getAge() - s2.getAge());
// 或者更安全地使用 Integer.compare
// students.sort((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
System.out.println("按年龄升序排序 (Lambda):");
students.forEach(System.out::println);
// 2. 按年龄降序排序
students.sort((s1, s2) -> s2.getAge() - s1.getAge());
System.out.println("\n按年龄降序排序 (Lambda):");
students.forEach(System.out::println);
}
}
注意: 从 Java 8 开始,List 接口本身提供了 sort(Comparator<? super E> c) 方法,所以可以直接调用 list.sort(),而不需要再通过 Collections.sort()。
Comparator 的链式调用(thenComparing)
在实际应用中,我们经常需要先按一个主条件排序,如果主条件相同,再按次条件排序。Comparator 提供了 thenComparing 方法来实现这种链式调用。
示例:先按年龄升序,如果年龄相同,再按姓名字典序排序。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(new Student("Bob", 20));
students.add(new Student("Charlie", 25));
students.add(new Student("David", 20)); // 和 Bob 年龄相同
// 链式调用
// 先按年龄升序
Comparator<Student> comparator = Comparator.comparingInt(Student::getAge)
// 如果年龄相同,则按姓名的字典序升序
.thenComparing(Student::getName);
students.sort(comparator);
System.out.println("先按年龄升序,年龄相同则按姓名升序:");
students.forEach(System.out::println);
}
}
输出结果:
先按年龄升序,年龄相同则按姓名升序:
Student{name='Bob', age=20}
Student{name='David', age=20}
Student{name='Alice', age=22}
Student{name='Charlie', age=25}
Student::getAge 是方法引用,是 Lambda 表达式的一种简写形式,Student::getName 也是同理。
Comparator 的常用静态方法(Java 8+)
Comparator 接口本身提供了许多非常有用的静态工厂方法,可以极大地简化代码。
| 静态方法 | 功能 | 示例 |
|---|---|---|
comparing( Function<T, U> keyExtractor) |
根据提取的 key 进行自然排序(升序)。 |
Comparator.comparing(Student::getAge) |
comparingInt( ToIntFunction<T> keyExtractor) |
专门用于 int 类型的 key,避免装箱/拆箱,效率更高。 |
Comparator.comparingInt(Student::getAge) |
comparingLong( ToLongFunction<T> keyExtractor) |
专门用于 long 类型的 key。 |
Comparator.comparingLong(Student::getAge) |
comparingDouble( ToDoubleFunction<T> keyExtractor) |
专门用于 double 类型的 key。 |
Comparator.comparingDouble(Student::getAge) |
naturalOrder() |
对实现了 Comparable 的对象进行自然排序。 |
Comparator.naturalOrder() |
reverseOrder() |
对实现了 Comparable 的对象进行反向排序。 |
Comparator.reverseOrder() |
nullsFirst(Comparator<? super T> comparator) |
将 null 值视为“最小”值,排在最前面。 |
Comparator.nullsFirst(...) |
nullsLast(Comparator<? super T> comparator) |
将 null 值视为“最大”值,排在最后面。 |
Comparator.nullsLast(...) |
示例:处理 null 值
假设我们的学生列表中可能包含 null 元素。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 22));
students.add(null);
students.add(new Student("Bob", 20));
students.add(null);
// 直接排序会抛出 NullPointerException
// students.sort(Comparator.comparingInt(Student::getAge));
// 使用 nullsFirst 将 null 值排在最前面
Comparator<Student> nullFirstComparator = Comparator.nullsFirst(
Comparator.comparingInt(Student::getAge)
);
students.sort(nullFirstComparator);
System.out.println("使用 nullsFirst 排序:");
students.forEach(System.out::println);
}
}
输出结果:
使用 nullsFirst 排序:
null
null
Student{name='Bob', age=20}
Student{name='Alice', age=22}
总结与最佳实践
-
选择
Comparable还是Comparator?- 如果这个类的“自然排序”是固定且公认的(如
String,Date,Integer),让它实现Comparable接口。 - 如果排序规则多种多样、临时需要,或者你无法修改被排序类的源码,请使用
Comparator。
- 如果这个类的“自然排序”是固定且公认的(如
-
现代 Java 的首选:
- 方法引用 (
Student::getAge) 比完整的 Lambda 表达式 (s -> s.getAge()) 更简洁、更具可读性。 - 静态工厂方法 (
Comparator.comparingInt()) 是创建Comparator实例最推荐、最安全、最高效的方式。 - 链式调用 (
thenComparing) 让处理多级排序变得非常优雅。
- 方法引用 (
-
代码示例(最佳实践版):
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 22),
new Student("Bob", 20),
new Student("Charlie", 25),
new Student("David", 20),
null
);
// 1. 按年龄升序(推荐方式)
List<Student> sortedByAgeAsc = students.stream()
.sorted(Comparator.comparingInt(Student::getAge))
.collect(Collectors.toList());
System.out.println("按年龄升序:");
sortedByAgeAsc.forEach(System.out::println);
// 2. 按年龄降序(推荐方式)
List<Student> sortedByAgeDesc = students.stream()
.sorted(Comparator.comparingInt(Student::getAge).reversed())
.collect(Collectors.toList());
System.out.println("\n按年龄降序:");
sortedByAgeDesc.forEach(System.out::println);
// 3. 先按年龄升序,再按姓名降序(链式调用)
List<Student> sortedByAgeAndName = students.stream()
.sorted(Comparator.comparingInt(Student::getAge)
.thenComparing(Student::getName, Comparator.reverseOrder()))
.collect(Collectors.toList());
System.out.println("\n先按年龄升序,再按姓名降序:");
sortedByAgeAndName.forEach(System.out::println);
// 4. 处理 null 值
List<Student> sortedWithNulls = students.stream()
.sorted(Comparator.nullsFirst(
Comparator.comparingInt(Student::getAge)
))
.collect(Collectors.toList());
System.out.println("\n处理 null 值:");
sortedWithNulls.forEach(System.out::println);
}
}
掌握 Comparator 是成为 Java 高级程序员的必经之路,它在数据处理、算法实现和日常开发中都扮演着至关重要的角色。
