【问题标题】:“Comparison method violates its general contract!” [duplicate]“比较法违反了它的总契约!” [复制]
【发布时间】:2018-03-05 07:29:57
【问题描述】:

我用谷歌搜索了一些相同的问题,我知道原因。但是崩溃只发生了一次,所以我来这里问你什么可能导致崩溃。

    Collections.sort(downloadTasks, new Comparator<DownloadTask>() {
        @Override
        public int compare(DownloadTask lhs, DownloadTask rhs) {
            if (lhs == null || rhs == null) return 0;
            return (int) (lhs.mTaskInfo.time - rhs.mTaskInfo.time);
        }
    });

错误是:

java.lang.IllegalArgumentException:比较方法违反了它的一般约定!在 java.util.TimSort.mergeHi(TimSort.java:864) 在 java.util.TimSort.mergeAt(TimSort.java:481) 在 java.util.TimSort.mergeCollapse(TimSort.java:406) 在 java.util.TimSort.sort(TimSort.java:210) 在 java.util.TimSort.sort(TimSort.java:169) 在 java.util.Arrays.sort(Arrays.java:2010) 在 java.util.Collections.sort(Collections.java:1883)

如您所见,我通过time 成员比较了两个对象。 timelong 类型。

我认为崩溃可能来自:

  • 将 long 转换为 int 是否会导致崩溃?
  • if (lhs == null || rhs == null) return 0; 但是理论上 lhs 和 rhs 都不是 null。

编辑

如果lhsrhs可以为null,我该怎么办?

为 Android 编辑

在 Android 中,Long.compare() 需要 API 19。您可以在 API 19 下执行此操作:

public static int compare(long lhs, long rhs) {
    return lhs < rhs ? -1 : (lhs == rhs ? 0 : 1);
}

【问题讨论】:

  • 什么崩溃?你会得到什么错误,什么时候,用什么代码,会发生什么,你期望会发生什么?看看How to Ask
  • 正如标题所说的错误是Comparison method violates its general contract!
  • 你能解释一下为什么演员阵容会导致崩溃吗? @Thilo
  • 我同意你的看法。你能写出你的答案吗?
  • 在减法之前尝试转换。

标签: java


【解决方案1】:

这两种情况都有问题。

if (lhs == null || rhs == null) return 0;

如果你有[123, null, 234],那么你比较123等于nullnull等于234,通过传递性你应该得到123等于234。但这不是您的比较器返回的结果。

这里的解决方案是禁止null 或将所有nulls 排序到底部(或顶部),即只返回0 两者 都是null,否则返回1-1(取决于 null 是左还是右)。

return (int) (lhs.mTaskInfo.time - rhs.mTaskInfo.time);

考虑将Integer.MAX_VALUE + 10 进行比较。两者的区别是Integer.MAX_VALUE + 1。将其转换为 int 将转换为 Integer.MIN_VALUE。相反的比较应该给你- Integer.MIN_VALUE,但that is Integer.MIN_VALUE again due to overflow

这里的解决方案是使用Long.compare(a,b)

【讨论】:

    【解决方案2】:

    比较方法的“通用合同”在compare(T o1, T o2)方法的javadoc中有很好的记录:

    实施者必须确保所有xy 都使用sgn(compare(x, y)) == -sgn(compare(y, x))。 (这意味着当且仅当compare(y, x) 抛出异常时,compare(x, y) 必须抛出异常。)

    实现者还必须确保关系是可传递的:((compare(x, y)&gt;0) &amp;&amp; (compare(y, z)&gt;0)) 隐含compare(x, z)&gt;0

    最后,实现者必须确保compare(x, y)==0 暗示sgn(compare(x, z))==sgn(compare(y, z)) 对于所有z。

    那么,您违反了这 3 条规则中的哪一条? 全部 3 个

    违反了第一条规则,因为timelong,所以(int) (lhs.mTaskInfo.time - rhs.mTaskInfo.time) 可以等于Integer.MIN_VALUE。如果将两个参数翻转为compare(),结果仍然是Integer.MIN_VALUE,因为int 值不能存储取反值。

    第二条规则被违反,因为int 溢出。由于timelong,我假设它们包含标准毫秒值,而int 最多只能存储24 天 的值。所以假设输入是Jan 1, Jan 15, Jan 30compare(Jan 1, Jan 15) 是 +14 天,compare(Jan 15, Jan 30) 是 +15 天,但 compare(Jan 1, Jan 30) 应该是 +29 天,但它溢出返回类似于 -19 天。糟糕!!!!

    第三条规则由于错误的空检查而被违反。假设输入是null, Jan 1, Jan 2compare(null, Jan 1) 是 0,compare(null, Jan 2) 也是,但 compare(Jan 1, Jan 2) 是 +1 天。

    解决方案

    int 溢出和MIN_VALUE 问题可以通过使用Long.compare(long x, long y) 轻松修复。

    对于null 问题,您需要决定空值应该排在最前面还是最后。以下将首先对空值进行排序:

    public int compare(DownloadTask lhs, DownloadTask rhs) {
        if (lhs == null) {
            if (rhs == null)
                return 0;
            return -1;
        }
        if (rhs == null)
            return 1;
        return Long.compare(lhs.mTaskInfo.time, rhs.mTaskInfo.time);
    }
    

    或单语句版本:

    public int compare(DownloadTask lhs, DownloadTask rhs) {
        return (lhs == null ? (rhs == null ? 0 : -1)
                : (rhs == null ? 1 : Long.compare(lhs.mTaskInfo.time, rhs.mTaskInfo.time)));
    }
    

    【讨论】:

    • 真棒和明确的答案。非常感谢。
    猜你喜欢
    • 2022-01-04
    • 1970-01-01
    • 2012-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多