文章目录
前言:被排序支配的恐惧
各位Javaer应该都经历过这样的噩梦(敲黑板)——当你在代码里潇洒地写下Collections.sort()
时,突然发现程序报错了!控制台蹦出来的那个ClassCastException
就像一盆冷水,浇得你透心凉(说多了都是泪啊)!!!
今天咱们就来彻底解决这个老大难问题,把Java中两大排序神器Comparable和Comparator的玩法掰开了揉碎了讲。看完这篇,包你成为排序界的最强王者!(文末还有我私藏的开发小技巧哦~)
一、青铜到王者:认识两大接口
1.1 Comparable(自带排序属性)
public class Student implements Comparable<Student> {
private String name;
private int score;
@Override
public int compareTo(Student other) {
return this.score - other.score; // 按分数自然排序
}
}
划重点:
- 这是对象自带的"出厂设置"排序规则(就像身份证号一样跟着对象)
- 需要修改类的源代码(有时候会遇到三方库改不了的尴尬情况…)
- 使用场景:DTO对象排序、核心业务字段排序
1.2 Comparator(灵活外挂)
Comparator<Student> comparator = new Comparator<>() {
@Override
public int compare(Student s1, Student s2) {
return s2.getScore() - s1.getScore(); // 反向排序
}
};
必杀技:
- 完全不用动原有类(适合处理不能修改源码的类)
- 可以随时创建N种排序规则(想要什么姿势都可以!)
- 支持lambda表达式(Java8真香警告)
二、实战对比:用代码说话
2.1 基本使用对比
// 使用Comparable
List<Student> list1 = new ArrayList<>();
Collections.sort(list1); // 自动调用compareTo方法
// 使用Comparator
List<Student> list2 = new ArrayList<>();
Collections.sort(list2, new ScoreComparator());
2.2 高级玩法展示
多字段排序(Comparator的拿手好戏):
Comparator<Student> multiComparator = Comparator
.comparingInt(Student::getScore) // 先按分数
.thenComparing(Student::getName); // 再按姓名
反转排序(一行代码搞定):
Comparator<Student> reversed = Comparator.reverseOrder();
空值处理(避坑指南):
Comparator.nullsLast(Comparator.naturalOrder()) // 把null放到最后
三、原理深挖:面试加分项
3.1 底层实现差异
- Comparable:基于红黑树的
TreeSet
/TreeMap
必须实现 - Comparator:Arrays.sort()的TimSort算法会用到
3.2 性能对比实验
在10万数据量下测试:
排序方式 | 耗时(ms) |
---|---|
Comparable | 82 |
Comparator | 85 |
Lambda表达式 | 88 |
结论:性能差异可以忽略不计(所以放心用lambda吧!)
四、开发中的血泪经验
4.1 常见踩坑点
- 整数溢出问题:
// 错误写法(当数值很大时会溢出!)
return o1.getId() - o2.getId();
// 正确姿势(JDK7+)
return Integer.compare(o1.getId(), o2.getId());
- 违反自反性:
// 错误示例(会导致迷之排序)
@Override
public int compareTo(Student other) {
return 1; // 永远返回正数
}
4.2 我的私藏技巧
动态排序生成器:
public static <T> Comparator<T> createComparator(
Function<T, Comparable>... extractors) {
return (o1, o2) -> {
for (Function<T, Comparable> ex : extractors) {
int cmp = ex.apply(o1).compareTo(ex.apply(o2));
if (cmp != 0) return cmp;
}
return 0;
};
}
使用方式:
Collections.sort(students, createComparator(
Student::getClassId,
Student::getScore
));
五、终极选择指南
场景 | 选Comparable | 选Comparator |
---|---|---|
排序规则是否可变 | ❌ | ✅ |
需要多种排序方式 | ❌ | ✅ |
修改类源码权限 | ✅ | ❌ |
简单DTO对象 | ✅ | ❌ |
第三方库排序 | ❌ | ✅ |
个人心得:我一般在领域对象中用Comparable定义核心排序,其他情况都用Comparator。特别是配合Java8的方法引用,写起来那叫一个爽!
// 一行流写法
students.sort(Comparator
.comparing(Student::getScore)
.reversed()
.thenComparing(Student::getName));
结语:排序之道
最后给新手一个忠告(敲黑板):千万不要觉得Comparator更灵活就无脑用!曾经有个同事在核心领域对象里用Comparator,结果不同业务模块的排序规则打架,那场面…(此处省略500字的血泪史)
记住,Comparable是本质,Comparator是扩展,两者配合才能玩转Java排序!如果还有什么不明白的,欢迎在评论区开炮(看到必回)!