java.lang.IllegalArgumentException: Comparison method violates its general contract!

解决方法:
Comparator的compare方法实现中添加return 0的情况。
原因:
在 JDK7 版本以上,Comparator 要满足自反性,传递性,对称性:
1) 自反性:x,y 的比较结果和 y,x 的比较结果相反。

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

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

4)强制要求有返回0的情况

博客中大部分解决方法都是这样的,也是可行的,但无法解释我在项目中遇到的情况。
事件复现:

  1. 公司后台模块管理页面,模块列表根据某属性进行排序
  2. 我在测试新增一条模块后页面刷新,报错,无法显示列表
  3. 查看后台日志,报错:

java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:777) ~[?:1.7.0_71]
at java.util.TimSort.mergeAt(TimSort.java:514) ~[?:1.7.0_71]
at java.util.TimSort.mergeCollapse(TimSort.java:441) ~[?:1.7.0_71]
at java.util.TimSort.sort(TimSort.java:245) ~[?:1.7.0_71]
at java.util.Arrays.sort(Arrays.java:1512) ~[?:1.7.0_71]
at java.util.ArrayList.sort(ArrayList.java:1454) ~[?:1.7.0_71]
at java.util.Collections.sort(Collections.java:175) ~[?:1.7.0_71]
at com.jd.o2o.task.service.impl.refactor.TaskConfigWebServiceImpl.getModels(TaskConfigWebServiceImpl.java:434) ~[?:?]

  1. 组长以为是我新增的模块格式有问题于是将其删除,随后列表恢复

源码分析:
排序使用 Collections.sort实现
Collections.sort方法调用了List.sort

public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
}

List.sort调用了Arrays.sort(这里由ArrayList重写,最终还是调用Arrays.sort)

	@Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

Arrays.sort源码:

public static <T> void sort(T[] a, int fromIndex, int toIndex,
                                Comparator<? super T> c) {
        if (c == null) {
            sort(a, fromIndex, toIndex);
        } else {
            rangeCheck(a.length, fromIndex, toIndex);
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, fromIndex, toIndex, c);
            else
                TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
        }
    }

这里如果没有设置

-Djava.util.Arrays.useLegacyMergeSort=true(JVM参数)

就会走TimSort.sort:

static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }

        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        TimSort<T> ts = new TimSort<>(a, c, work, workBase, workLen);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi, c);

            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen, c);
                runLen = force;
            }

            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();

            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);

        // Merge all remaining runs to complete sort
        assert lo == hi;
        ts.mergeForceCollapse();
        assert ts.stackSize == 1;
    }

注意方法的第二个if判断:

// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
     int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
     binarySort(a, lo, hi, lo + initRunLen, c);
     return;
 }

其中 MIN_MERGE:

private static final int MIN_MERGE = 32;

也就是说当list长度小于32时进入这里,通过binarySort后直接返回
如果大于32,走TimSort.mergeCollapse,最终调用到TimSort.mergeLo(或TimSort.mergeHi),报错也就是从这里抛出的:

private void mergeLo(int base1, int len1, int base2, int len2) {
        .....
        .....
        .....

        if (len1 == 1) {
            assert len2 > 0;
            System.arraycopy(a, cursor2, a, dest, len2);
            a[dest + len2] = tmp[cursor1]; //  Last elt of run 1 to end of merge
        } else if (len1 == 0) {
            throw new IllegalArgumentException(
                "Comparison method violates its general contract!");
        } else {
            assert len2 == 0;
            assert len1 > 1;
            System.arraycopy(tmp, cursor1, a, dest, len1);
        }
    }

数了一下新增前的模块列表,正好31个,且之前的代码没有写return 0的情况,于是出现了新增一条数据就报错,删除后不报错的假象。

你可能感兴趣的:(java)