解决方法:
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的情况
博客中大部分解决方法都是这样的,也是可行的,但无法解释我在项目中遇到的情况。
事件复现:
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) ~[?:?]
源码分析:
排序使用 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的情况,于是出现了新增一条数据就报错,删除后不报错的假象。