解决 Comparison method violates its general contract!

本贴最后更新于 1445 天前,其中的信息可能已经事过境迁

一、背景

昨天在使用公司的某个平台时,意外遇到了一个问题:

Comparison method violates its general contract!

以前没有见过这个异常,于是拿这个异常在网上搜了一下,发现是 TimSort 排序导致的,这里简单记录下。

二、复现 + 测试代码

JDK 版本:

**

master@jiangmufeng ~ $ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

报错异常:

**

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.TimSort.mergeLo(TimSort.java:777)
    at java.util.TimSort.mergeAt(TimSort.java:514)
    at java.util.TimSort.mergeCollapse(TimSort.java:441)
    at java.util.TimSort.sort(TimSort.java:245)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at java.util.Collections.sort(Collections.java:175)
    at sort.test.Main.sort(Main.java:31)
    at sort.test.Main.main(Main.java:21)

代码示例:

**

/**
 * Test TimSort
 * @author jiangmufeng
 * @date 2020-02-25 14:24
 **/
public class Main {
    public static void main(String[] args) {

        sort(1, 1, 1, 1, 1, 2, 1, 1, 1);
        sort(3, 2, 3, 2, 1, 31);
        sort(2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3);

        // exception
        sort(1, 2, 3, 2, 2, 3, 2, 3, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);

    }

    private static void sort(Integer... ints) {
        List<Integer> list = Arrays.asList(ints);
        list.sort((o1, o2) -> {
            if (o1 < o2) {
                return -1;
            } else {
                return 1;
            }
        });
        System.out.println(list);
    }
}
三、原因

查看下文档,可以简单看出,这是 JDK7 与之前版本之间的一个小的兼容问题。当然,这个兼容性问题也延续到了当前的 JDK8 版本:

Area: API: Utilities

Synopsis: Updated sort behavior for Arrays and Collections may throw an IllegalArgumentException

Description: The sorting algorithm used by java.util.Arrays.sort and (indirectly) by java.util.Collections.sort has been replaced. The new sort implementation may throw an IllegalArgumentException if it detects a Comparable that violates the Comparable contract. The previous implementation silently ignored such a situation.

If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort, to restore previous mergesort behavior.

Nature of Incompatibility: behavioral

RFE: 6804124

如果有兴趣,可以看下以下链接(Java SE 7 and JDK 7 Compatibility):https://www.oracle.com/technetwork/java/javase/compatibility-417013.html

因为 JDK7 以后,Arrays.sort 方法换了排序方式,使用 TimSort 来进行排序,新的实现在自定义比较器违背比较规则的情况下有可能会抛出异常,原来的实现则是忽略了这个异常。所以为了保证不抛出异常,对比较器的比较规则要求比较严格:

  • sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
  • (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0
  • x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z))

简而言之,就是以下三个方面:

  • 自反性:x 与 y 的比较结果和 y 与 x 的比较结果相反;
  • 传递性:如果 x>y 并且 y>z, 那么 x>z;
  • 对称性:如果 x=y, 那么 x 与 z 的比较结果和 y 与 z 的比较结果相同;

而如果需要使用 JDK7 之前的实现方式,可以通过增加系统属性 java.util.Arrays.useLegacyMergeSort 恢复使用原来的排序规则。

PS:当然这只是有可能出现 IllegalArgumentException 异常,并不是一定,这个取决于 TimSort 的内部实现,这个可以看下源码。

四、解决

解决方式自然也很简单:

  1. 增加系统属性: java.util.Arrays.useLegacyMergeSort;
  2. 比较时,严格按照规则,尽量返回 0,-1,1 这三个标准结果来进行比较。

image.png

代码示例来源:

https://programtalk.com/java/comparison-method-violates-general-contract/

https://www.harinathk.com/java/sort-algorithm-changes-java-7-throws-illegalargumentexception/

作者:骑着乌龟去看海
链接:https://www.jianshu.com/p/4e568ef541ae
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
zhaozhizheng
没有人会关心你付出过多少努力,撑得累不累,摔得痛不痛,他们只会看你最后站在什么位置,然后羡慕或者鄙夷 北京