问题
Comparator 与 Comparable 有何区别?
答案
核心概念
- Comparable:内部比较器,定义对象的自然排序(默认排序规则)
- 实现类:
class User implements Comparable<User> - 方法:
int compareTo(T o) - 特点:侵入式,需要修改原类
- 实现类:
- Comparator:外部比较器,定义对象的定制排序(临时排序规则)
- 实现类:
class AgeComparator implements Comparator<User> - 方法:
int compare(T o1, T o2) - 特点:非侵入式,无需修改原类
- 实现类:
1. Comparable 接口
基本用法
public class Student implements Comparable<Student> {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 定义自然排序:按分数升序
@Override
public int compareTo(Student other) {
return this.score - other.score;
// 返回值:
// < 0:this < other(this 排在前面)
// = 0:this == other(相等)
// > 0:this > other(this 排在后面)
}
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 85),
new Student("李四", 92),
new Student("王五", 78)
);
// 使用自然排序
Collections.sort(students);
// 结果:王五(78), 张三(85), 李四(92)
}
}
JDK 中的典型实现
// 1. Integer 的自然排序:数值升序
Integer i1 = 10;
Integer i2 = 20;
System.out.println(i1.compareTo(i2)); // -1(i1 < i2)
// 2. String 的自然排序:字典序
String s1 = "apple";
String s2 = "banana";
System.out.println(s1.compareTo(s2)); // -1(a < b)
// 3. Date 的自然排序:时间先后
Date d1 = new Date(2024, 1, 1);
Date d2 = new Date(2024, 12, 31);
System.out.println(d1.compareTo(d2)); // -1(d1 早于 d2)
注意事项:避免溢出
public class BadComparable implements Comparable<BadComparable> {
private int value;
@Override
public int compareTo(BadComparable other) {
// 错误:可能溢出
return this.value - other.value;
// 例如:value = Integer.MAX_VALUE, other.value = -1
// 结果:Integer.MAX_VALUE - (-1) = 溢出为负数
}
}
public class GoodComparable implements Comparable<GoodComparable> {
private int value;
@Override
public int compareTo(GoodComparable other) {
// 正确:使用 Integer.compare()
return Integer.compare(this.value, other.value);
// 或者使用三元运算符
// return this.value < other.value ? -1 : (this.value == other.value ? 0 : 1);
}
}
2. Comparator 接口
基本用法
public class Student {
private String name;
private int score;
private int age;
// 无需实现 Comparable
}
// 定制比较器1:按分数降序
public class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s2.score, s1.score); // 降序
}
}
// 定制比较器2:按年龄升序
public class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return Integer.compare(s1.age, s2.age); // 升序
}
}
public class Test {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 85, 20),
new Student("李四", 92, 19),
new Student("王五", 78, 21)
);
// 使用不同的比较器
Collections.sort(students, new ScoreComparator());
// 结果:李四(92), 张三(85), 王五(78)
Collections.sort(students, new AgeComparator());
// 结果:李四(19), 张三(20), 王五(21)
}
}
Lambda 表达式简化(Java 8+)
public class Test {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("张三", 85, 20),
new Student("李四", 92, 19),
new Student("王五", 78, 21)
);
// 1. Lambda 表达式
students.sort((s1, s2) -> Integer.compare(s2.getScore(), s1.getScore()));
// 2. 方法引用 + Comparator 工具方法
students.sort(Comparator.comparing(Student::getScore).reversed());
// 3. 多字段排序:先按分数降序,再按年龄升序
students.sort(Comparator.comparing(Student::getScore).reversed()
.thenComparing(Student::getAge));
// 4. 处理 null 值
students.sort(Comparator.nullsFirst(
Comparator.comparing(Student::getName)
));
}
}
3. Comparator 的高级用法
1. 静态工厂方法(Java 8+)
public class ComparatorDemo {
public static void main(String[] args) {
List<Student> students = getStudents();
// 1. comparing:提取比较键
students.sort(Comparator.comparing(Student::getScore));
// 2. comparingInt/Long/Double:避免装箱
students.sort(Comparator.comparingInt(Student::getScore));
// 3. reversed:反转排序
students.sort(Comparator.comparing(Student::getScore).reversed());
// 4. thenComparing:多级排序
students.sort(Comparator.comparing(Student::getScore)
.thenComparing(Student::getName));
// 5. nullsFirst/nullsLast:null 值处理
students.sort(Comparator.nullsFirst(
Comparator.comparing(Student::getName)
));
// 6. naturalOrder/reverseOrder:自然排序
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5);
numbers.sort(Comparator.naturalOrder()); // 升序
numbers.sort(Comparator.reverseOrder()); // 降序
}
}
2. 复杂排序场景
public class ComplexSortDemo {
public static void main(String[] args) {
List<Employee> employees = getEmployees();
// 场景1:先按部门升序,再按薪资降序,最后按姓名升序
employees.sort(Comparator.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName));
// 场景2:自定义比较逻辑
employees.sort(Comparator.comparing(e -> {
// VIP 员工优先
return e.isVip() ? 0 : 1;
}).thenComparing(Employee::getSalary, Comparator.reverseOrder()));
// 场景3:忽略大小写排序
List<String> names = Arrays.asList("Alice", "bob", "Charlie");
names.sort(String.CASE_INSENSITIVE_ORDER);
}
}
4. 对比总结
| 特性 | Comparable | Comparator |
|---|---|---|
| 包路径 | java.lang.Comparable | java.util.Comparator |
| 方法 | int compareTo(T o) | int compare(T o1, T o2) |
| 实现位置 | 在被比较的类内部 | 在独立的比较器类中 |
| 排序类型 | 自然排序(默认排序) | 定制排序(临时排序) |
| 侵入性 | 侵入式(需修改原类) | 非侵入式(无需修改原类) |
| 灵活性 | 一个类只能有一种自然排序 | 可以有多个不同的比较器 |
| 使用场景 | 对象有明确的默认排序规则 | 需要多种排序方式或无法修改原类 |
| 典型应用 | Integer, String, Date 等 | Collections.sort(), Arrays.sort() |
5. 使用场景选择
使用 Comparable 的场景
// 1. 对象有明确的自然排序
public class Product implements Comparable<Product> {
private String id;
private double price;
@Override
public int compareTo(Product other) {
// 商品默认按价格升序排序
return Double.compare(this.price, other.price);
}
}
// 2. 类的设计者可以控制排序规则
// 例如:JDK 的 Integer, String, Date 等
使用 Comparator 的场景
// 1. 无法修改原类(第三方库、JDK 类)
List<String> strings = Arrays.asList("apple", "banana", "cherry");
strings.sort(Comparator.comparing(String::length)); // 按长度排序
// 2. 需要多种排序方式
List<Student> students = getStudents();
students.sort(new ScoreComparator()); // 按分数排序
students.sort(new AgeComparator()); // 按年龄排序
students.sort(new NameComparator()); // 按姓名排序
// 3. 临时排序需求
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));
// 4. 复杂的多字段排序
students.sort(Comparator.comparing(Student::getGrade)
.thenComparing(Student::getScore)
.thenComparing(Student::getName));
6. 实际应用案例
案例1:TreeSet 的排序
public class TreeSetDemo {
public static void main(String[] args) {
// 方式1:使用 Comparable(自然排序)
Set<Integer> set1 = new TreeSet<>();
set1.addAll(Arrays.asList(5, 2, 8, 1, 9));
System.out.println(set1); // [1, 2, 5, 8, 9]
// 方式2:使用 Comparator(定制排序)
Set<Integer> set2 = new TreeSet<>(Comparator.reverseOrder());
set2.addAll(Arrays.asList(5, 2, 8, 1, 9));
System.out.println(set2); // [9, 8, 5, 2, 1]
}
}
案例2:PriorityQueue 的排序
public class PriorityQueueDemo {
public static void main(String[] args) {
// 默认:小顶堆(使用 Comparable)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
minHeap.addAll(Arrays.asList(5, 2, 8, 1, 9));
System.out.println(minHeap.poll()); // 1
// 大顶堆(使用 Comparator)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());
maxHeap.addAll(Arrays.asList(5, 2, 8, 1, 9));
System.out.println(maxHeap.poll()); // 9
}
}
案例3:Stream 排序
public class StreamSortDemo {
public static void main(String[] args) {
List<Student> students = getStudents();
// 使用 Comparable
List<Integer> scores = students.stream()
.map(Student::getScore)
.sorted() // 自然排序
.collect(Collectors.toList());
// 使用 Comparator
List<Student> sorted = students.stream()
.sorted(Comparator.comparing(Student::getScore).reversed())
.collect(Collectors.toList());
}
}
7. 性能优化建议
public class PerformanceDemo {
// 1. 避免重复计算
// 不推荐
list.sort((s1, s2) -> s1.getName().toLowerCase().compareTo(s2.getName().toLowerCase()));
// 推荐:提前计算
list.sort(Comparator.comparing(s -> s.getName().toLowerCase()));
// 2. 使用基本类型比较器(避免装箱)
// 不推荐
list.sort(Comparator.comparing(Student::getScore));
// 推荐
list.sort(Comparator.comparingInt(Student::getScore));
// 3. 缓存比较器实例
private static final Comparator<Student> SCORE_COMPARATOR =
Comparator.comparingInt(Student::getScore);
list.sort(SCORE_COMPARATOR);
}
答题总结
面试要点:
- 核心区别:
- Comparable 是内部比较器,定义自然排序,需修改原类
- Comparator 是外部比较器,定义定制排序,无需修改原类
- 方法签名:
- Comparable:
int compareTo(T o)(单参数) - Comparator:
int compare(T o1, T o2)(双参数)
- Comparable:
- 使用场景:
- Comparable:对象有明确的默认排序规则(如 Integer, String)
- Comparator:需要多种排序方式或无法修改原类
- 最佳实践:
- 避免使用减法比较(防止溢出),使用
Integer.compare() - Java 8+ 推荐使用 Lambda 和
Comparator.comparing() - 多字段排序使用
thenComparing() - 基本类型使用
comparingInt/Long/Double避免装箱
- 避免使用减法比较(防止溢出),使用
记忆口诀:
- Comparable 内部定,一个类一种排序
- Comparator 外部传,灵活多变随意换