必须用Comparable当类有唯一不可争议的自然排序依据,如Person按身份证号排序;否则应优先用Comparator实现多场景灵活排序。

什么时候必须用 Comparable?
当你定义的类有且仅有一个「天然、唯一、不可争议」的排序依据时,比如 Person 按身份证号排序、Order 按订单创建时间排序,就该让类自己实现 Comparable。这样它才能直接放进 TreeSet 或作为 TreeMap 的 key,否则会抛 ClassCastException。
关键点:
-
compareTo()是实例方法,调用者是当前对象(this),参数是另一个同类型对象; - 一旦实现,这个排序逻辑就“长在类里”,所有使用者都默认走这一套;
- 如果类已发布、被其他模块引用,再改
compareTo可能破坏下游逻辑——它不是“可插拔”的。
为什么 Comparator 更适合日常开发?
现实中,同一类数据往往需要多种排序:用户列表既要按注册时间升序,又要按积分降序,还要按昵称拼音排序。这时候硬塞进 Comparable 就会失控——你不可能让一个类同时实现三个自然顺序。
正确做法是把排序逻辑外置:
立即学习“Java免费学习笔记(深入)”;
- 写多个
Comparator实现类,或直接用 Lambda:(a, b) -> Integer.compare(b.getScore(), a.getScore()); - 传给
Collections.sort(list, comparator)或list.stream().sorted(comparator); - 支持 null 安全:比如
Comparator.nullsLast(Comparator.naturalOrder()),而Comparable遇到null几乎必崩。
compareTo 和 compare 返回值别写错
两个接口都靠返回 int 值表达大小关系,但新手常犯两类错误:
- 用减法计算导致整数溢出:
return this.age - other.age在 age 是Integer.MAX_VALUE和Integer.MIN_VALUE时会反向;应改用Integer.compare(this.age, other.age); - 比较字符串时直接用
==或忽略大小写/本地化需求,比如name1.compareTo(name2)不等于name1.equalsIgnoreCase(name2),更不等于按中文拼音排序; - 自定义比较器中没处理相等场景(比如两个对象字段都为 null),可能违反
Comparator的「一致性契约」,引发TreeSet行为异常。
Lambda 写 Comparator 时最容易漏什么?
Java 8+ 后,90% 的 Comparator 场景都用 Lambda,但以下三点常被跳过:
- 链式排序没加
thenComparing:想先按年龄、再按姓名,不能写两个独立 Lambda,得用Comparator.comparing(Person::getAge).thenComparing(Person::getName); - 逆序写成
(a,b) -> b-a而非Comparator.reverseOrder()或reversed(),前者不兼容 null,后者可组合; - 对基本类型包装类(如
Integer)直接解包比较:(a,b) -> a.id - b.id,万一a.id是 null 就 NPE——应先用Comparator.nullsFirst(Comparator.comparing(...))。
真正难的不是选哪个接口,而是意识到:自然顺序只属于领域模型本身,而排序策略属于使用场景。把「谁该决定怎么排」这件事想清楚,比记住语法重要得多。










