目录
- 一、为什么需要比较器?
- 二、核心差异速记表
- 三、Comparable:对象自带的 “默认规则”
- 1. 核心作用
- 2. 源码定义
- 3. 实战:给Student类加默认规则
- 4. 源码验证(以Integer为例)
- 四、Comparator:临时的 “外部规则”
- 1. 核心作用
- 2. 源码定义
- 3. 实战:给 Student 加临时规则
- 方式 1:匿名内部类(传统写法)
- 方式 2:Lambda 表达式(Java 8+ 简化写法)
- 4. 源码验证(以 Arrays.sort 为例)
- 4.1 Arrays.sort()
- 4.1.1 无Comparator时
- 4.1.2 有 Comparator 时
- 4.2 TreeMap中的比较器优先级
- 五、Comparator 的进阶
- 1. 多条件排序(先按 A,再按 B)
- 2. 空值处理(允许 null)
- 3. 反转顺序
- 六、最佳实践总结
- 1.比较器三原则(违反会导致排序异常):
- 2.整数比较的陷阱
- 3.选择策略:
- 4.JDK8+高效写法:
- 七、记忆口诀(一句话记住核心)
- 1.Comparable:
- 2.Comparator:
- 3.优先级:
- 八、常见面试题
- 1.Comparable 和 Comparator 的区别?
- 2.如果一个类没有实现 Comparable,能否排序?
- 3.compareTo 和 compare 方法的返回值有什么要求?
- 4.如何对自定义对象排序?
- 5.Comparator 有哪些常用方法?
- 6.如何实现逆序排序?
- 7.比较器在HashMap中的作用?
一、为什么需要比较器?
场景:
- 对 Integer、String 等内置类型排序时,JDK 知道怎么比大小(如 1 < 2,“a” < “b”)。
- 但对自定义对象(如 User、Product),JDK 不知道按什么规则排序(按年龄?按价格?)。
比较器就是用来定义 “对象比较规则” 的工具。
二、核心差异速记表
Comparable(自然排序) | Comparator(定制排序) | |
---|---|---|
包位置 | java.lang | java.util |
接口方法 | compareTo(T o) | compare(T o1, T o2) |
使用场景 | 类本身的默认排序规则 | 临时定义多种排序策略 |
规则位置 | 写在类内部(实现接口) | 写在类外部(单独定义) |
修改代码 | 需要修改类源码 | 不修改原有类 |
排序方式 | 单一自然排序 | 支持多种排序规则 |
典型应用 | String、Integer等包装类的排序 | 第三方库排序,多条件排序 |
三、Comparable:对象自带的 “默认规则”
1. 核心作用
让对象 “自带” 一个比较规则,就像人天生知道 “年龄大的更年长” 一样。只要对象实现了Comparable,就能直接用Arrays.sort() 或Collections.sort() 排序。
2. 源码定义
// JDK 源码(java.lang.Comparable)
public interface Comparable<T> {int compareTo(T o); // 定义比较规则的方法
}
- 返回值含义(重点!):
- 如果 this < o → 返回负数(比如 -1)。
- 如果 this == o → 返回 0。
- 如果 this > o → 返回正数(比如 1)。
3. 实战:给Student类加默认规则
假设Student默认按分数升序排序:
public class Student implements Comparable<Student> {private String name;private int score;// 重写 compareTo:定义“分数升序”规则@Overridepublic int compareTo(Student other) {return this.score - other.score; // 分数小的排前面}// 构造方法、getter 省略...
}
使用示例:
Student[] students = {new Student("张三", 85),new Student("李四", 75),new Student("王五", 90)
};Arrays.sort(students); // 直接排序!因为 Student 实现了 Comparable
// 排序后顺序:李四(75)、张三(85)、王五(90)
4. 源码验证(以Integer为例)
Integer能直接排序,因为它实现了Comparable:
// JDK 源码(java.lang.Integer)
public final class Integer implements Comparable<Integer> {public int compareTo(Integer another) {return this.value - another.value; // 按数值大小比较}
}
四、Comparator:临时的 “外部规则”
1. 核心作用
当对象没有实现Comparable,或者需要临时改变排序规则(比如Student平时按分数排,但今天要按姓名排),就用Comparator。
2. 源码定义
// JDK 源码(java.util.Comparator)
@FunctionalInterface // 函数式接口(可 Lambda)
public interface Comparator<T> {int compare(T o1, T o2); // 定义比较规则的方法
}
- 返回值含义和 Comparable 一致:
- o1 < o2 → 负数;o1 == o2 → 0;o1 > o2 → 正数。**
3. 实战:给 Student 加临时规则
需求:Student平时按分数排(Comparable),但今天需要按姓名长度降序排。
方式 1:匿名内部类(传统写法)
// 定义一个“姓名长度降序”的比较器
Comparator<Student> nameLengthComparator = new Comparator<Student>() {@Overridepublic int compare(Student s1, Student s2) {// 姓名长度大的排前面(降序)return s2.getName().length() - s1.getName().length();}
};// 使用这个比较器排序
Arrays.sort(students, nameLengthComparator);
// 排序后顺序:张三(2字)、李四(2字)、王五(2字)→ 若长度相同,保持原顺序
方式 2:Lambda 表达式(Java 8+ 简化写法)
// 用 Lambda 简化 Comparator 定义
Comparator<Student> nameLengthComparator = (s1, s2) -> s2.getName().length() - s1.getName().length();Arrays.sort(students, nameLengthComparator); // 效果同上
4. 源码验证(以 Arrays.sort 为例)
4.1 Arrays.sort()
// 在TimSort(Java排序算法实现)中的关键代码
if (c.compare(a[runHi++], a[lo]) < 0) { // 使用比较器判断顺序// 执行元素交换等操作
}
Arrays.sort 有两种重载,分别对应 Comparable 和 Comparator:
4.1.1 无Comparator时
public static <T extends Comparable<? super T>> void sort(T[] a) {// 直接调用对象的compareTo方法TimSort.sort(a, 0, a.length, null, 0, 0);
}
约束:数组元素必须实现Comparable,否则报ClassCastException。
4.1.2 有 Comparator 时
public static <T> void sort(T[] a, Comparator<? super T> c) {// 使用传入的ComparatorTimSort.sort(a, 0, a.length, c, 0, 0);
}
灵活性:无需元素实现Comparable,临时传入规则即可。
4.2 TreeMap中的比较器优先级
public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator; // 比较器优先于自然排序
}final int compare(Object k1, Object k2) {return comparator==null ? ((Comparable)k1).compareTo(k2): comparator.compare(k1, k2);
}
五、Comparator 的进阶
1. 多条件排序(先按 A,再按 B)
需求:学生先按分数降序,分数相同则按姓名升序。
// 组合比较器:先按分数降序,分数相同按年龄升序
Comparator<Student> complexComparator = Comparator.comparingInt(Student::getScore).reversed() // 分数降序(先主条件).thenComparingInt(Student::getAge);// 分数相同,按姓名升序(次条件)Arrays.sort(students, multiComparator);// 相当于:
(s1, s2) -> {int scoreCompare = Integer.compare(s2.getScore(), s1.getScore());return (scoreCompare != 0) ? scoreCompare : Integer.compare(s1.getAge(), s2.getAge());
};
2. 空值处理(允许 null)
// null 排在最前面(nullsFirst)
Comparator<Student> nullsFirstComparator = Comparator.nullsFirst(Comparator.comparing(Student::getScore));// null 排在最后面(nullsLast)
Comparator<Student> nullsLastComparator = Comparator.nullsLast(Comparator.comparing(Student::getScore));
3. 反转顺序
Comparator<Student> scoreAsc = Comparator.comparingInt(Student::getScore); // 分数升序
Comparator<Student> scoreDesc = scoreAsc.reversed(); // 反转成降序
六、最佳实践总结
1.比较器三原则(违反会导致排序异常):
自反性:compare(a, a) == 0
对称性:compare(a, b) == -compare(b, a)
传递性:若compare(a, b) > 0且compare(b, c) > 0,则compare(a, c) > 0
2.整数比较的陷阱
// 错误写法(可能溢出):
return o1.id - o2.id;// 正确写法:
return Integer.compare(o1.id, o2.id);
3.选择策略:
- 类有自然顺序 → 实现Comparable
- 需要多种排序方式 → 使用Comparator
- 第三方类排序 → 必须用Comparator
4.JDK8+高效写法:
// 多字段排序
users.sort(Comparator.comparing(User::getLastName).thenComparing(User::getFirstName));// 按字符串长度排序
Comparator.comparing(String::length);
七、记忆口诀(一句话记住核心)
1.Comparable:
- 类内定义 “默认规则”,
- 所有排序都用它(如学生默认按分数);
- 例子:User implements Comparable,重写 compareTo。
2.Comparator:
- 类外定义 “临时规则”,
- 哪里需要哪里传(如学生临时按姓名);
- 例子:Collections.sort(users, (u1, u2) -> …)。
3.优先级:
当Comparable和Comparator同时存在时,Comparator优先(覆盖默认规则)。
八、常见面试题
1.Comparable 和 Comparator 的区别?
答:Comparable 是类内部实现的接口(compareTo),定义对象的默认排序规则;Comparator 是外部定义的接口(compare),用于临时修改排序规则。
2.如果一个类没有实现 Comparable,能否排序?
答:可以!通过 Comparator 传入临时规则(如 Arrays.sort(数组, 自定义Comparator))。
3.compareTo 和 compare 方法的返回值有什么要求?
答:必须满足:负数(前者小)、0(相等)、正数(前者大)。如果返回值逻辑错误,排序会乱。
4.如何对自定义对象排序?
答:两种方式:
① 让类实现Comparable接口,重写compareTo。
② 不修改类,创建Comparator并传给排序方法。
5.Comparator 有哪些常用方法?
答:reversed()(反转)、thenComparing()(多条件排序)、nullsFirst()/nullsLast()(空值处理)。
6.如何实现逆序排序?
// 方法1:反转比较结果
Comparator<Student> reversed = (s1, s2) -> s2.compareTo(s1);// 方法2:使用内置方法
Comparator.comparing(Student::getScore).reversed();
7.比较器在HashMap中的作用?
HashMap不依赖比较器,但TreeMap的排序依赖比较器