Comparison method violates its general contract报错解决

Comparison method violates its general contract报错解决可以看到在调用 TimSort.sort 时, java.lang.IllegalArgumentException: Comparison m

大家好,欢迎来到IT知识分享网。
Comparison

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 异常。

  1. 自反性:x,y 的比较结果和 y,x 的比较结果相反,即 sgn(compare(x,y)) == -sgn(compare(y,x))。
  2. 传递性:x > y,y > z,则 x > z,即 ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0。
  3. 对称性: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. 方法1:强制 JVM 使用老旧的 MergeSort,而非新的 TimSort。

(1) 可以在代码层面上进行声明

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true")

(2) 也可以在 JVM 的启动参数中声明

-Djava.util.Arrays.useLegacyMergeSort=true
  1. 方法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); }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/73821.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信