Comparison method violates its general contract!

一、背景

昨天在使用公司的某個平臺時,意外遇到了一個問題:

Comparison method violates its general contract!

以前沒有見過這個異常,于是拿這個異常在網(wǎng)上搜了一下,發(fā)現(xiàn)是TimSort排序導致的,這里簡單記錄下。

二、復現(xiàn)+測試代碼

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與之前版本之間的一個小的兼容問題。當然,這個兼容性問題也延續(xù)到了當前的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來進行排序,新的實現(xiàn)在自定義比較器違背比較規(guī)則的情況下有可能會拋出異常,原來的實現(xiàn)則是忽略了這個異常。所以為了保證不拋出異常,對比較器的比較規(guī)則要求比較嚴格:

  • 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之前的實現(xiàn)方式,可以通過增加系統(tǒng)屬性 java.util.Arrays.useLegacyMergeSort 恢復使用原來的排序規(guī)則。

PS:當然這只是有可能出現(xiàn)IllegalArgumentException異常,并不是一定,這個取決于TimSort的內(nèi)部實現(xiàn),這個可以看下源碼。

四、解決

解決方式自然也很簡單:

  1. 增加系統(tǒng)屬性: java.util.Arrays.useLegacyMergeSort;
  2. 比較時,嚴格按照規(guī)則,盡量返回0,-1,1這三個標準結果來進行比較。

代碼示例來源:
https://programtalk.com/java/comparison-method-violates-general-contract/
https://www.harinathk.com/java/sort-algorithm-changes-java-7-throws-illegalargumentexception/

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容