Ref
- ref-1-Comparison method violates its general contract!
- why does my compare method throw exception — Comparison method violates its general contract!
- ref-2-Java中排序报:Comparison method violates its general contract异常的解决
- ref-3-JAVA 排序异常:java.lang.IllegalArgumentException:Comparison method violates its general contract!
问题描述
public class FixedPromotionMaterialInfoComparator implements Comparator<PromotionMaterialInfo> {
@Override
public int compare(PromotionMaterialInfo o1, PromotionMaterialInfo o2) {
try{
if(StringUtils.isNotBlank(o1.getSortNum()) && StringUtils.isNotBlank(o2.getSortNum())){
int sortNum1 = PromotionMaterialUtils.getSortNum(o1.getSortNum(), EasyUtils.toString(o1.getForceType()));
int sortNum2 = PromotionMaterialUtils.getSortNum(o2.getSortNum(),EasyUtils.toString(o2.getForceType()));
if(sortNum1 == sortNum2){ //页码相等 按照有效开始时间升序排序
Map<String, Object> extMap1 = JsonUtil.json2Map(EasyUtils.toString(o1.getExt()));
Map<String, Object> extMap2 = JsonUtil.json2Map(EasyUtils.toString(o2.getExt()));
Long startTimeVal1 = EasyUtils.parseLong(Optional.ofNullable(extMap1).orElseGet(HashMap::new).get("validStartTime"));
Long startTimeVal2 = EasyUtils.parseLong(Optional.ofNullable(extMap2).orElseGet(HashMap::new).get("validStartTime"));
return (int) (startTimeVal1 - startTimeVal2);
}
return sortNum1 - sortNum2; //页码不等 按照页码升序排序
}
}catch (Exception e){
log.error("FixedPromotionMaterialInfoComparator exception,o1:{},o2:{}",JsonUtil.write2JsonStr(o1),JsonUtil.write2JsonStr(o2));
}
return 0;
}
}
复制代码
如上代码所示,线上运行后偶现如下报错日志。可以看到在调用 TimSort.sort
时, java.lang.IllegalArgumentException: Comparison method violates its general contract
,比较方法违背了其约定的规则。
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:868)
at java.util.TimSort.mergeAt(TimSort.java:485)
at java.util.TimSort.mergeCollapse(TimSort.java:408)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
复制代码
问题分析
在 JDK6 及更老版本的中,上述代码运行是不会产生异常的。从 JDK7 开始,
Collections.sort()
在排序算法上的更新固然能够带来排序性能上的提升,但这一次排序算法的升级对比较器Comparator
增加了一些规则,并没有完全向前兼容。
在 JDK7 版本以上,Comparator
要满足自反性,传递性,对称性,不然 Arrays.sort
,Collections.sort
会报 IllegalArgumentException
异常。
- 自反性:x,y 的比较结果和 y,x 的比较结果相反,即
sgn(compare(x,y)) == -sgn(compare(y,x))
。 - 传递性:x > y,y > z,则 x > z,即
((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0
。 - 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。即
compare(x,y)==0 implies that sgn(compare(x,z))==sgn(compare(y,z)) for all z
。
因此,对于上述代码,compare(PromotionMaterialInfo o1, PromotionMaterialInfo o2)
,若 o1.startTimeVal1 = o2.startTimeVal1
,则会出现
compare(o1,o2) == compare(o2,o1)
的情况,这违反了自反性质。从而导致代码抛出异常。
x=null; y=yFile; z=zFile
compare(x,y)==0; compare(x,z)==0;
但compare(y,z)不一定==0,不能保证sgn(compare(x, z))==sgn(compare(y, z))。
问题解决
- 方法1:强制 JVM 使用老旧的 MergeSort,而非新的 TimSort。
(1) 可以在代码层面上进行声明
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
复制代码
(2) 也可以在 JVM 的启动参数中声明
-Djava.util.Arrays.useLegacyMergeSort=true
复制代码
- 方法2:修改代码,使得比较器
Comparator
满足新算法自反性、传递性、对称性的要求
public class FixedPromotionMaterialInfoComparator implements Comparator<PromotionMaterialInfo> {
@Override
public int compare(PromotionMaterialInfo o1, PromotionMaterialInfo o2) {
try{
if(StringUtils.isNotBlank(o1.getSortNum()) && StringUtils.isNotBlank(o2.getSortNum())){
...
//return (int) (startTimeVal1 - startTimeVal2);
return startTimeVal1.compareTo(startTimeVal2); //修改为此语句
}
...
}catch (Exception e){
log.error("FixedPromotionMaterialInfoComparator exception,o1:{},o2:{}",JsonUtil.write2JsonStr(o1),JsonUtil.write2JsonStr(o2));
}
return 0;
}
}
复制代码
其中 compareTo()
源码如下。
/**
* Compares two {@code Long} objects numerically.
*
* @param anotherLong the {@code Long} to be compared.
* @return the value {@code 0} if this {@code Long} is
* equal to the argument {@code Long}; a value less than
* {@code 0} if this {@code Long} is numerically less
* than the argument {@code Long}; and a value greater
* than {@code 0} if this {@code Long} is numerically
* greater than the argument {@code Long} (signed
* comparison).
* @since 1.2
*/
public int compareTo(Long anotherLong) {
return compare(this.value, anotherLong.value);
}
/**
* Compares two {@code long} values numerically.
* The value returned is identical to what would be returned by:
* <pre>
* Long.valueOf(x).compareTo(Long.valueOf(y))
* </pre>
*
* @param x the first {@code long} to compare
* @param y the second {@code long} to compare
* @return the value {@code 0} if {@code x == y};
* a value less than {@code 0} if {@code x < y}; and
* a value greater than {@code 0} if {@code x > y}
* @since 1.7
*/
public static int compare(long x, long y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
复制代码
近期评论