前言
在业务开发中,我们经常会遇到对一个集合对象里面的元素按一定规则排序的需求。在Java中,通常我们会调用集合对象对应的sort方法,传入一个Comparator的匿名内部类,来实现业务需求。
背景
恰巧最近我在业务中就碰到这么个需求,需要对包含Staff的List集合对象按照Staff的注册时间倒序返回结果。
Staff定义:
public class Staff{
// 员工的注册时间
private Date registerDate;
.
.
.
}主要业务代码:
...
List<Staff> staffs = staffRepository.findByUser(account.getUser());
if(staffs != null && staffs.size() > 0) {
// 主要代码,根据注册时间倒序返回staff记录
staffs.sort((o1, o2) -> {
if (o1.getRegisterDate().after(o2.getRegisterDate())) {
return -1;
} else {
return 1;
}
}
);
...问题
当时写完,随便跑了下也没问题,就没放在心上。直到前两天(也就是清明节)的凌晨一点,一顿亡命电话call,把我从睡(mei)梦(shui)中叫醒,我才知道自己犯了一个多蠢的错误。
同事当时发过来一段线上日志:Comparison method violates its general contract!
这段日志的大致意思是,比较方法违反了它的自然原则。google一下,发现原来是自己错误使用Comparator导致的。
分析
翻下官方文档对compare方法的doc解释:
其中最重要的就是中间讲到的三点:
- sgn(compare(x,y)) == -sgn(compare(y,x))
- compare(x,y)>0 && compare(y,z)>0 ==> compare(x,z)>0
- compare(x,y)==0 ==> 对任意z,都有sgn(compare(x,z))==sgn(compare(y,z))
回想我们上面的业务代码实现,显然第一点都没有满足: 当o1, o2 的registerDate相同时,o1, o2 在参数中位置的不同会导致排序结果的不同,自相矛盾。
解决方案
把关键的业务代码替换为:
staffs.sort((o1, o2) -> - o1.getRegisterDate().compareTo(o2.getRegisterDate())即可解决问题。对于相同注册时间的o1, o2 元素,会返回0(即二者在排序上相同),不会因为参数顺序出现自相矛盾的结果。
总结
在实现自定义的排序规则时,一定要考虑清楚,检查是否符合上面说所的三个原则。