什么是 Comparable 接口?
Comparable 接口位于 java.lang 包中,它定义了一个对象的“自然排序”(natural ordering),如果一个类实现了 Comparable 接口,就意味着该类的对象之间可以相互比较,并且可以定义一个默认的、内在的排序规则。

Comparable 让一个对象“知道”自己应该排在另一个对象的前面还是后面。
Comparable 接口的核心方法
Comparable 接口中只定义了一个方法:
public int compareTo(T o);
这个方法用于“比较当前对象”与“传入对象 o”之间的大小关系。
compareTo 方法的返回值规则:
- 返回负整数:表示当前对象 小于 传入对象
o,在排序时,当前对象应该排在o的前面。 - 返回零:表示当前对象 等于 传入对象
o,在排序时,它们的相对位置可以任意。 - 返回正整数:表示当前对象 大于 传入对象
o,在排序时,当前对象应该排在o的后面。
一个重要的约定:为了保持排序的一致性,compareTo 方法必须满足以下三个条件(类似 equals 方法的约定):

- 自反性:
x.compareTo(x)必须返回 0。 - 对称性:
x.compareTo(y)返回 0,y.compareTo(x)也必须返回 0。 - 传递性:
x.compareTo(y)返回负数,且y.compareTo(z)也返回负数,x.compareTo(z)必须返回负数。
如何使用 Comparable 接口?(实践示例)
让我们通过一个经典的例子:对一个 Student 类进行排序,我们希望按照学生的年龄进行排序。
步骤 1: 创建 Student 类并实现 Comparable 接口
import java.util.Objects;
// Student 类实现了 Comparable<Student> 接口
// 这里的泛型 <Student> 表示这个类可以和另一个 Student 对象进行比较
public class Student implements Comparable<Student> {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
// Getter 方法
public String getName() { return name; }
public int getAge() { return age; }
public int getScore() { return score; }
// 实现 compareTo 方法,定义“自然排序”规则
@Override
public int compareTo(Student other) {
// 比较规则:按照年龄升序排序
// 如果当前对象的年龄小于 other 对象的年龄,返回负数
if (this.age < other.age) {
return -1;
}
// 如果当前对象的年龄大于 other 对象的年龄,返回正数
else if (this.age > other.age) {
return 1;
}
// 如果年龄相等,返回 0
else {
return 0;
}
// 更简洁的写法 (利用 int 的自动减法):
// return this.age - other.age;
}
// 为了方便打印,重写 toString 方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
步骤 2: 使用 Collections.sort() 进行排序
Collections.sort() 方法可以对一个实现了 Comparable 接口的 List 进行排序。
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, 90));
students.add(new Student("Bob", 20, 85));
students.add(new Student("Charlie", 22, 88));
students.add(new Student("David", 19, 95));
System.out.println("排序前的学生列表:");
for (Student s : students) {
System.out.println(s);
}
// 使用 Collections.sort() 进行排序
// 因为 Student 类实现了 Comparable,所以可以直接排序
Collections.sort(students);
System.out.println("\n按照年龄升序排序后的学生列表:");
for (Student s : students) {
System.out.println(s);
}
}
}
输出结果:
排序前的学生列表:
Student{name='Alice', age=22, score=90}
Student{name='Bob', age=20, score=85}
Student{name='Charlie', age=22, score=88}
Student{name='David', age=19, score=95}
按照年龄升序排序后的学生列表:
Student{name='David', age=19, score=95}
Student{name='Bob', age=20, score=85}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}
从结果可以看到,Student 对象已经按照年龄从小到大成功排序。

Comparable vs. Comparator
这是 Java 开发中一个非常常见的面试题,它们都与排序有关,但用途和设计理念不同。
| 特性 | Comparable |
Comparator |
|---|---|---|
| 位置 | java.lang 包 |
java.util 包 |
| 定义 | 在类的内部定义排序规则 | 在类的外部定义排序规则 |
| 方式 | 类实现 Comparable 接口,重写 compareTo 方法 |
创建一个独立的比较器类,实现 Comparator 接口,重写 compare 方法 |
| 数量 | 一个类只能有一个 compareTo 方法(即一种自然排序) |
一个类可以有多个 Comparator(即多种不同的排序方式) |
| 使用场景 | 当对象的“自然排序”是明确且固定的时候,字符串按字典序,数字按数值大小。 | 当需要根据不同标准排序,或者不想修改/不能修改源类的时候。 |
| 方法 | int compareTo(T o) |
int compare(T o1, T o2) |
Comparator 的示例(延续上面的 Student 类)
假设我们除了想按年龄排序,还想按分数排序,我们不希望修改 Student 类,这时 Comparator 就派上用场了。
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, 90));
students.add(new Student("Bob", 20, 85));
students.add(new Student("Charlie", 22, 88));
students.add(new Student("David", 19, 95));
// 1. 使用 Comparable 的自然排序(按年龄)
Collections.sort(students);
System.out.println("按年龄排序:");
students.forEach(System.out::println);
// 2. 使用 Comparator 进行自定义排序(按分数降序)
// 创建一个匿名内部类作为比较器
Comparator<Student> scoreComparator = new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
// 降序排序,所以用 s2 减 s1
return s2.getScore() - s1.getScore();
}
};
// Java 8+ 的 Lambda 表达式写法(更简洁)
// Comparator<Student> scoreComparator = (s1, s2) -> s2.getScore() - s1.getScore();
System.out.println("\n按分数降序排序:");
Collections.sort(students, scoreComparator);
students.forEach(System.out::println);
}
}
输出结果:
按年龄排序:
Student{name='David', age=19, score=95}
Student{name='Bob', age=20, score=85}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}
按分数降序排序:
Student{name='David', age=19, score=95}
Student{name='Alice', age=22, score=90}
Student{name='Charlie', age=22, score=88}
Student{name='Bob', age=20, score=85}
Comparable是“内比较器”:它让类自己具备可比较性,定义了“自然排序”,当你调用Collections.sort(list)且 list 中的元素实现了Comparable时,就会使用compareTo方法进行排序。Comparator是“外比较器”:它是一个独立的、外部的排序规则,当你需要多种排序方式,或者不能/不想修改源类时,使用Comparator,你需要在Collections.sort(list, comparator)中显式地传入比较器。- 选择哪个?
- 如果一个类的排序规则是它固有的、唯一的,那么让它实现
Comparable。 - 如果一个类需要多种排序方式,或者排序规则是动态变化的,或者你无法修改源类,那么使用
Comparator。
- 如果一个类的排序规则是它固有的、唯一的,那么让它实现
在实际开发中,Comparator 因其灵活性和不污染源代码的特性,使用频率可能更高,尤其是在 Java 8 引入 Lambda 表达式之后,使用 Comparator 变得非常方便和优雅。
