转载

工作中的疑难问题-Comparison method violates its general contract!异常

工作中的疑难问题-Comparison method violates its general contract!异常

刚开始看到这个问题,异常里提示at java.util.TimSort.mergeHi(TimSort.java:868)即TimSort类的mergeHi方法抛出的。

查看了Collections的sort方法源码,结果如下图

工作中的疑难问题-Comparison method violates its general contract!异常

那为什么会出现这种异常呢?

因为JDK7中的Collections.Sort方法实现中,如果两个值是相等的,那么compare方法需要返回0,否则 可能 会在排序时抛错,而JDK6是没有这个限制的。

if (len2 == 0) {

throw new IllegalArgumentException("Comparison method violates its general contract!");

}

在 JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort,

Collections.sort 会报 IllegalArgumentException 异常。

说明:

1) 自反性:x,y 的比较结果和 y,x 的比较结果相反。

2) 传递性:x>y,y>z,则 x>z。

3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。(也叫可逆比较)

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

复现

Collections.sort(list, new Comparator() {

@Override

public int compare(Integer o1, Integer o2) {

return o1 > o2 ? 1 : -1;// 错误的方式

}

});

解决方案

先说如何解决,解决方式有两种。

修改代码方式1(官方的)

Collections.sort(list, new Comparator() {

@Override

public int compare(Integer o1, Integer o2) {

// return o1 > o2 ? 1 : -1;

return o1.compareTo(o2);// 正确的方式

}

});

修改代码方式1(自定义的)

Collections.sort(list, new Comparator() {

@Override

public int compare(Integer o1, Integer o2) {

if(o1 > o2) {

return 1;

} else if (o1< o2){

return -1;

} else {

//考虑==这种场景,同时注意o1 o2不为空

//假设a>b b>c 那么a>c一定成立的。

//目前上面的例子存在 问题是如果o1为null,则在任何情况下,都是null>o2,

//但是其实存在o2==null的情况,这就导致了null> null 的逻辑错误

return 0;

}

}

});

(想要真正解决这个问题,并不是像很多网上帖子上所说加个等于条件就可以,这个是因为逆比较引发的问题,就应该从根源上解决这个问题:就是根据自己的代码逻辑判断是否一定符合上述的自反性等特性,后面我会找出特殊的场景)

不修改代码

那么问题来了。为什么上面代码在JDK6中运行无问题,而在JDK7中却会抛异常呢?这是因为JDK7底层的排序算法换了,如果要继续使用JDK6的排序算法,可以在JVM的启动参数中加入如下参数:

-Djava.util.Arrays.useLegacyMergeSort=true

这样就会照旧使用JDK6的排序算法,在不能修改代码的情况下,解决这个兼容的问题。(这种本人测试未达到效果 可能是操作问题,我不建议使用这种 这种的弊端在我看来会导致你无法使用jdk1.7里面的新特性,对后期的升级是有不可知的影响的)

初步结论:

那么现在是否可以盖棺定论了,按照上面的分析来看,使用这种比较方式(return x > y ? 1 : -1;),只要集合或数组中有相同的元素,就会抛出本文标题的异常。实则不然,什么情况下抛出异常,还取决于JDK7底层排序算法的实现,也就是大名鼎鼎的TimSort。后面文章会分析TimSort。本文给出一个会引发该异常的Case,以便有心人共同研究,如下:

Integer[] array =

{0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2, 1, 0, 0, 0, 2, 30, 0, 3};

进一步探讨:

上述的东西是我查了一天的结论,但是依然没有解决我项目中实际的问题,下面这个例子才跟我实际的情况比较相似

国外例子

下面代码,你可以看出为什么也会报这个错误吗?(这种场景隐藏的比较深,不反复思考根本发现不了)

工作中的疑难问题-Comparison method violates its general contract!异常

外国人解释:

比较方法是不传递的。举个例子如果 A==B 和B==C,那么

A一定等于C。

现在看这个例子的情况:

假设A、B、C三个对象情况。假设包含情况是这样的:

childMap.containsKey(A.getID()) returns true

childMap.containsKey(B.getID()) returns false

childMap.containsKey(C.getID()) returns true

当A和B比较的时候,外面的if条件不满足,所以返回结果是0,意味着A==B

当B和C比较的时候,外面的if条件仍然不满足,所以结果也是0,意味着B==C。

假设A和C比较的时候很有可能返回1或-1 ,这就造成的结果是A!=C.

这就违反了传递规则。

所以不能在else里面直接返回0,需要根据情况判断。

至此我的问题才总算解决了.

参考博客:

blog.csdn.net/TomCosin/ar…

www.cnblogs.com/firstdream/…

www.jianshu.com/p/aebaa787c…
原文  https://juejin.im/post/5e83e9ae51882573c15ecbbf
正文到此结束
Loading...