java.lang.IllegalArgumentException: Comparison method violates its general contract!
处置其中无法想到的BUG

原则

Java7开始引入了更高级合理的比较机制,比以往的compare更加严格,原则上要满足自反性,传递性,对称性,否则就会报 IllegalArgumentException 异常,即Comparison method violates its general contract。

那么三个性质分别是:

  • 自反性:x、y 的比较结果和 y、x 的比较结果相反
  • 传递性:x>y,y>z,则 x>z
  • 对称性:x=y,则 x、z 比较结果和 y、z 比较结果相同

但是很多描述这个事情的文章,普遍先说一个事情,比较的时候o1和o2如果有null怎么办,所以重点就引导到了compare方法要返回三个结果[ -1 , 0 , 1 ]。那如果你不仔细,就会走向这个结果而忽略了前述的三个原则。比如示例一段如下类似的代码。

if(o1 == null && o2 == null) {  
    return 0;  
}  
if(o1 == null) {  
    return -1;  
}  
if(o2 == null) {  
    return 1;  
}  
if(o1.getCreateTime() > o2.getCreateTime()) {  
    return 1;  
}  
if(o2.getCreateTime() > o1.getCreateTime()) {  
    return -1;  
}  
return 0;  

症状

业务的需求是汇总本流程和子流程的留言,然后此处需要一个排序,有两个参数可用于比较,一个是浏览时间,一个是节点序号。于是肯定需要自己构建compare方法了,对浏览时间和节点序号分别递进对比,完成排序。那么按照前述的后半部分情况对比o1和o2时,考虑如果一个是null(主要是浏览时间为空,即尚未浏览),那就返回1,即算作大,不为空的那个为小。所以这段大概是这样的。

private static int compare(JSONObject o1, JSONObject o2) {
	String time1 = o1.getString("Time");
	String time2 = o2.getString("Time");
	Date d1, d2;
	try {
		d1 = UtilDate.parseDatetime(time1);
	} catch (Exception e) {
		return 1;
	}
	try {
		d2 = UtilDate.parseDatetime(time2);
	} catch (Exception e) {
		return -1;
	}
	int i = d1.compareTo(d2);
	if (i == 0) {
		if (!UtilString.isEmpty(o1.getString("No")) && !UtilString.isEmpty(o2.getString("No"))) {
			int a = o1.getInteger("No");
			int b = o2.getInteger("No");
			if (a > b) {
				return 1;
			} else {
				return a == b ? 0 : -1;
			}
		} else {
			return 0;
		}
	} else {
		return i;
	}
}

但是系统偶尔会报告一个错误就是Comparison method violates its general contract。那肯定是前述原则没有保持。但是看起来没有毛病啊,按照-1、0、1返回了。更可笑的是,如果出现问题,重启另外一个内部程序,这个就能解决。一度我认为Java在内存使用上存在某些泄露,JVM参数没有调好么,只能靠重启解决。

解决

今天又遇上了,重启大法——不,不能当鸵鸟,得好好研究研究。把所有运行态数据全都扒拉出来,然后构建一个本地程序跑一下。然后,恍 ~ 然 ~ 大 ~ 悟 ~ 。

根据原则中的那段代码,如果o1 == null && o2 == null,返回了0,而我的那段代码,处理逻辑是一旦看到o1为null则o1大,而不再关心o2了,恰少了这么一个判断。怎么发现的呢:debug过程comparator要拿o1和o1比较即自己比较自己,恰巧o1的time不可被parse,所以compare(o1, o1)会产生1的结果,即自己比自己大。或者说违反了自反性,如果o1 == null && o2 == null时,compare(o1, o2)和compare(o2, o1) 会产生 o1 > o2 和 o2 > o1 的两个结果。

嗐,自罚三杯