文章目录
一、对象排序的世纪难题
各位Javaer们(敲黑板)!!!咱们每天写代码是不是经常遇到这样的场景:给用户列表按注册时间排序、商品按价格排序、订单按金额排序…(说多了都是泪)这时候就该搬出咱们的排序双雄——Comparable和Comparator了!
但等等(突然警觉)!这哥俩到底有什么区别?什么时候该用谁?为什么我用了Comparable排序却总是不生效?今天咱们就来扒开这两个接口的底裤,哦不,底层原理!(手动狗头)
二、Comparable接口:天生的排序基因
1. 基因检测报告
public class Student implements Comparable<Student> {
private String name;
private int score;
@Override
public int compareTo(Student other) {
// 按分数降序排列(划重点)
return other.score - this.score;
}
}
2. 核心特性(必考!)
- 内嵌式排序:就像刻在DNA里的排序规则(天生自带比较能力)
- 侵入性强:需要修改目标类的源代码(像纹身一样难以去除)
- 自然排序:Collections.sort(students) 直接使用
3. 实战踩坑实录
// 错误示范:比较字段类型不一致
public int compareTo(Student other) {
// 这里把int转成Integer会产生性能损耗!
return Integer.valueOf(score).compareTo(other.score);
}
// 正确姿势:直接使用基本类型比较
public int compareTo(Student other) {
return score - other.score; // 适用于小范围数值
}
(血泪教训)特别注意:直接相减的方式在数值较大时可能溢出!安全写法应该使用Integer.compare(a, b)
三、Comparator接口:灵活的策略大师
1. 七十二变的排序策略
Comparator<Student> byHeight = (s1, s2) ->
Integer.compare(s1.getHeight(), s2.getHeight());
Comparator<Student> byWeight = Comparator.comparingInt(Student::getWeight);
2. 核心优势(面试常问)
- 非侵入式:不需要修改原有类结构(给别人的类加排序也不慌)
- 多重策略:同一个类可以有N种排序方式(想怎么排就怎么排)
- 链式组合:多个条件组合排序超方便
3. 高级玩法大公开
// 多重条件排序(先按年级倒序,再按学号正序)
Comparator<Student> complexComparator = Comparator
.comparingInt(Student::getGrade).reversed()
.thenComparing(Student::getStudentId);
// null值处理(把null对象放到最后)
Comparator<Student> nullSafeComparator = Comparator
.nullsLast(Comparator.naturalOrder());
四、世纪对决:双接口全方位PK表
| 维度 | Comparable | Comparator |
|---|---|---|
| 包位置 | java.lang | java.util |
| 实现方式 | 实现接口 | 匿名内部类/lambda |
| 排序方法 | compareTo() | compare() |
| 使用场景 | 自然排序 | 定制排序 |
| 侵入性 | 需要修改类 | 完全解耦 |
| 排序策略数量 | 每个类只能有1种 | 无限多种 |
| JDK8支持 | 需要实现接口 | 内置多种工厂方法 |
(超级重要)黄金选择原则:
- 当类有明确的自然排序时 → Comparable
- 需要多种排序方式时 → Comparator
- 无法修改源码的类 → 必须用Comparator
五、底层原理透视镜
1. Comparable的排序奥秘
当调用Arrays.sort()时,实际走的是TimSort算法:
// JDK源码片段(简化版)
static void sort(Object[] a) {
if (a instanceof Comparable[]) {
// 使用自然排序
compareTo方法被调用
} else {
throw new ClassCastException();
}
}
2. Comparator的策略模式
Comparator完美体现了策略模式:
// 排序时传入不同的Comparator实现
Collections.sort(students, new WeightComparator());
Collections.sort(students, new HeightComparator());
(底层黑科技)Java8的Comparator.comparing()方法其实是用了方法引用+函数式编程:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
return (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
六、性能对决:谁更快?
咱们实测说话!测试10万个Student对象排序:
| 比较方式 | 耗时(ms) |
|---|---|
| Comparable | 45 |
| Lambda表达式 | 48 |
| 匿名内部类 | 65 |
| 方法引用 | 47 |
(意外发现)Lambda表达式居然比匿名内部类快!这是因为JVM对lambda做了特殊优化
七、新时代的排序革命(Java8+)
1. 链式比较的优雅写法
List<Student> sorted = students.stream()
.sorted(Comparator.comparing(Student::getGrade)
.thenComparing(Student::getScore.reversed()))
.collect(Collectors.toList());
2. 空指针安全的比较器
Comparator<Student> nullSafe = Comparator
.comparing(Student::getName, Comparator.nullsLast(Comparator.naturalOrder()));
3. 自定义Comparator的工厂方法
Comparator<Student> weirdComparator = Comparator
.comparingInt(s -> (s.getScore() % 100)) // 按分数末两位排序
.thenComparing(s -> s.getName().length());
八、终极选择指南(决策树)
当面临排序需求时:
- 这个类有没有天然的顺序? → 是:用Comparable
- 需要多种排序方式? → 是:用Comparator
- 排序第三方库的类? → 必须用Comparator
- 需要组合多个排序条件? → 用Comparator的thenComparing()
- 要处理null值? → 用Comparator.nullsFirst/nullsLast
九、常见翻车现场
1. Comparable实现不一致问题
// 错误实现:违反compareTo与equals一致性
@Override
public int compareTo(Student other) {
return this.score - other.score; // 当score相等时compareTo返回0
}
@Override
public boolean equals(Object obj) {
// 但equals比较的是name...
return this.name.equals(((Student)obj).name);
}
(灵魂拷问)这样的类放进TreeSet会发生什么?元素会莫名消失!
2. Comparator的排序稳定性
// 错误示范:非确定性比较器
Comparator<Student> badComparator = (s1, s2) -> {
if(Math.random() > 0.5) return 1; // 这TM是排序还是抽奖?
return s1.getScore() - s2.getScore();
};
(拍桌)这种比较器会导致排序结果随机变化,绝对禁止!
十、总结升华
最后给各位的忠告(敲黑板三次):
- 能用Comparator尽量用,解耦才是王道
- 实现compareTo方法时,一定要与equals保持一致
- 数值比较优先用Integer.compare()避免溢出
- 多条件排序善用thenComparing()
- 处理null值要用专门的nullsFirst/nullsLast
记住(突然鸡汤):好的排序实现,就像人生一样,既要有内在的准则(Comparable),也要有适应环境的灵活性(Comparator)!

被折叠的 条评论
为什么被折叠?



