文章目录
一、排序场景引发的灵魂拷问
老铁们有没有遇到过这样的场景(敲黑板)?当你用Collections.sort()
给对象列表排序时,突然发现控制台抛出ClassCastException
!!!这酸爽,简直比喝了隔夜咖啡还上头。这时候你才惊觉——原来Java根本不知道该怎么给你的自定义对象排序!
(举个栗子🌰)假设我们有个学生类:
public class Student {
String name;
int score;
}
当调用Collections.sort(students)
时,Java会直接给你甩脸色看。这时候就该我们的两位主角登场了——Comparable
和Comparator
这对黄金搭档!
二、Comparable:天生自带的比较基因
2.1 接口定义解析
public interface Comparable<T> {
int compareTo(T o);
}
这个接口就像给对象植入的DNA,让对象天生具备比较能力。注意看(敲重点):
- 必须实现
compareTo()
方法 - 参数类型是泛型T(通常就是当前类)
- 返回int值:负数表示小于,0表示相等,正数表示大于
2.2 实战代码示例
改造我们的Student类:
public class Student implements Comparable<Student> {
String name;
int score;
@Override
public int compareTo(Student other) {
// 按分数降序排列(划重点!)
return other.score - this.score;
}
}
这时候调用Collections.sort(students)
就能完美排序了!但等等…如果产品经理突然说要支持按姓名排序怎么办?
三、Comparator:灵活多变的外挂装备
3.1 接口结构解剖
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
这个接口就像瑞士军刀(超实用!):
- 是独立的外部比较器
- 支持lambda表达式(Java8+)
- 可以创建多个不同比较规则
3.2 多种使用姿势
姿势一:匿名内部类
Comparator<Student> nameComparator = new Comparator<>() {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareTo(s2.name);
}
};
姿势二:lambda表达式(真香!)
Comparator<Student> nameComparator =
(s1, s2) -> s1.name.compareTo(s2.name);
姿势三:方法引用(逼格更高)
Comparator<Student> nameComparator =
Comparator.comparing(Student::getName);
四、世纪对决:接口差异全解析
维度 | Comparable | Comparator |
---|---|---|
包位置 | java.lang | java.util |
排序方法 | compareTo() | compare() |
使用场景 | 自然排序(默认排序规则) | 定制排序(多种排序规则) |
侵入性 | 需要修改原有类 | 不修改原有类 |
排序方式 | 内部排序(自己和自己比) | 外部排序(两个对象对比) |
线程安全 | 非线程安全 | 线程安全(推荐使用) |
空值处理 | 不支持null比较 | 支持nullsFirst/nullsLast |
(灵魂画手上线)举个实际项目中的例子:电商系统里商品默认按价格排序(适合用Comparable),但促销时又要按折扣率排序(这时就该用Comparator了)!
五、高级玩法:组合技展示
5.1 多条件排序
Comparator<Student> complexComparator = Comparator
.comparingInt(Student::getScore).reversed() // 分数降序
.thenComparing(Student::getName); // 姓名升序
5.2 空值处理
Comparator<Student> nullSafeComparator = Comparator
.nullsLast(Comparator.comparing(Student::getName));
5.3 JDK8新特性
students.sort(Comparator
.comparingInt(Student::getScore)
.thenComparing(Student::getName));
六、避坑指南(血泪教训)
- 整数溢出陷阱:
// 错误示范!!!
public int compareTo(Student other) {
return this.score - other.score; // 可能溢出!
}
// 正确姿势✅
public int compareTo(Student other) {
return Integer.compare(this.score, other.score);
}
-
违反传递性:
假设三个学生A(80)、B(null)、C(90),如果Comparator没处理null,排序结果可能不一致! -
equals方法一致性:
如果a.compareTo(b)==0
,理论上应该满足a.equals(b)
,否则可能引发Collections.sort()的玄学bug!
七、性能优化小贴士
对于大数据量排序:
- 优先使用基本类型比较(避免自动装箱)
- 对于不可变类,可以缓存Comparator实例
- 考虑使用并行流排序(谨慎使用!)
List<Student> parallelSorted = students.parallelStream()
.sorted(complexComparator)
.collect(Collectors.toList());
八、源码级深度理解
扒一扒Arrays.sort()的底层实现:
- 当元素个数 < 47:插入排序
- 47 <= 个数 < 286:快速排序
-
= 286:归并排序(TimSort)
(重点来了!)排序算法会根据是否传入Comparator走不同分支:
- 自然排序(Comparable)走ComparableTimSort
- 定制排序(Comparator)走TimSort
九、面试加分项
被问到这两个接口的区别时,可以这样装逼:
“从JVM角度看,Comparable接口的compareTo方法调用属于虚方法调用(invokevirtual),而Comparator的compare方法调用是通过接口方法表(itable)查找的。在JIT优化后,后者可能产生更多的内联缓存…”
十、总结与选择策略
最后送上选择口诀(快截图!):
- 默认排序用Comparable(一劳永逸)
- 多种排序用Comparator(灵活多变)
- 框架集成看需求(比如TreeSet用Comparable)
- 空值处理选Comparator(安全第一)
- 性能优化要实测(别瞎猜)
下次当你准备撸排序代码时,不妨先问问自己:这次是要给对象植入DNA(Comparable),还是带上瑞士军刀(Comparator)?选择困难症不存在的!