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:899)
    at java.util.TimSort.mergeAt(TimSort.java:516)
    at java.util.TimSort.mergeForceCollapse(TimSort.java:457)
    at java.util.TimSort.sort(TimSort.java:254)
    at java.util.Arrays.sort(Arrays.java:1432)
    at com.tencent.qcloud.core.logger.FileLogAdapter.getLogFile(FileLogAdapter.java:157)
    at com.tencent.qcloud.core.logger.FileLogAdapter.write(FileLogAdapter.java:194)
    at com.tencent.qcloud.core.logger.FileLogAdapter.flush(FileLogAdapter.java:220)
    at com.tencent.qcloud.core.logger.FileLogAdapter.access$000(FileLogAdapter.java:30)
    at com.tencent.qcloud.core.logger.FileLogAdapter$1.handleMessage(FileLogAdapter.java:85)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:176)
    at android.os.HandlerThread.run(HandlerThread.java:65)

看堆栈信息显示,由Cos SDK 内部导致的错误。意思是说:在排序的比较器方法中,其比较逻辑,违反了比较规则,故抛错如上。

扒开代码看了一下:

Arrays.sort(logFiles, new Comparator() {
    @Override
    public int compare(File lhs, File rhs) {
        return (int) (rhs.lastModified() - lhs.lastModified());
    }
});

即:此比较器逻辑,抛出了错误(5.4.21)。

找出来了问题,那么如何处理呢?

官网文档看了下,发现有新的版本,那么就更新试试吧。

更新至 5.4.29

果然,此处的比较器逻辑已经被修改了:

Arrays.sort(logFiles, new Comparator() {
    @Override
    public int compare(File lhs, File rhs) {
        return Long.valueOf(rhs.lastModified()).compareTo(Long.valueOf(lhs.lastModified()));
    }
});

分析&总结

首先上述异常原因:比较器逻辑违反了比较规则。

那么为什么第一种写法违反了规则呢?

查看Comparator 的文档:

/**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.

* * In the foregoing description, the notation * sgn(expression) designates the mathematical * signum function, which is defined to return one of -1, * 0, or 1 according to whether the value of * expression is negative, zero or positive.

* * The implementor must ensure that sgn(compare(x, y)) == * -sgn(compare(y, x)) for all x and y. (This * implies that compare(x, y) must throw an exception if and only * if compare(y, x) throws an exception.)

* * The implementor must also ensure that the relation is transitive: * ((compare(x, y)>0) && (compare(y, z)>0)) implies * compare(x, z)>0.

* * Finally, the implementor must ensure that compare(x, y)==0 * implies that sgn(compare(x, z))==sgn(compare(y, z)) for all * z.

* * It is generally the case, but not strictly required that * (compare(x, y)==0) == (x.equals(y)). Generally speaking, * any comparator that violates this condition should clearly indicate * this fact. The recommended language is "Note: this comparator * imposes orderings that are inconsistent with equals." * * @param o1 the first object to be compared. * @param o2 the second object to be compared. * @return a negative integer, zero, or a positive integer as the * first argument is less than, equal to, or greater than the * second. * @throws NullPointerException if an argument is null and this * comparator does not permit null arguments * @throws ClassCastException if the arguments' types prevent them from * being compared by this comparator. */ int compare(T o1, T o2);

得知,使用Compare需要满足的比较规则是:

记 x,y,z 三个参数:

  • 规则1:

若 sgn(compare(x,y))>0 且 sgn(compare(y,z)>0, 则 sgn(compare(x,z))>0

  • 规则2:

若 sgn(compare(x,y))==0 且 sgn(compare(y,z))==0 则 sgn(compare(x,z)==0

...

由此上述第一种比较器逻辑中,有一个long to int 的强制类型转换,如此由于强转int可能出现溢出的情况,则导致 x,y,z 的部分组合下,出现违背规则的情况。

换一种方式 假如不是 long to int ,而是 float to int

x,y,z 分别是 1.7,1.4,0.7

若 比较器逻辑为 return (int)(a-b)

sgn(compare(x,y)) == 0, sgn(compare(y,z)==0,
但是 sgn(compare(x,z) > 0

即 违背了规则2

类似这种的还有 参与排序比较的 包含数字与字符串混杂的情况,也易出现违背规则的情况。

总结

  1. 熟悉掌握比较规则
  2. 自定义比较器时,时刻保持警惕
  3. 这种逻辑本身属于:知道了就可以避免,不知道的话,无从下手。

你可能感兴趣的:(java.lang.IllegalArgumentException: Comparison method violates its general contract!)