【问题标题】:.compareTo() with 2 Sorting-Columns.compareTo() 具有 2 个排序列
【发布时间】:2012-07-14 18:54:55
【问题描述】:

我正在尝试在 Java 中为需要按两个不同列/变量排序的对象实现可比较的接口。我尝试了多种方法,这是迄今为止最好的一种:

public int compareTo(Object o) {
    Match m = (Match)o;
    int diff = m.matches - matches;
    if (diff == 0) {
        if (distance > m.distance) {
            return 1;
        } else if (distance < m.distance) {
            return -1;
        } else {
            return 0;
        }
    } else {
        return diff;
    }
}

但它仍然失败

java.lang.IllegalArgumentException: Comparison method violates its general contract!

任何想法我做错了什么?

旁注 1:如果 o 为 null 或属于不合适的类,则预期会出现 NPE/ClassCastExceptions - 这不是这里的问题。

旁注 2:我知道 JDK 1.7 中排序算法中的 change,但我并没有真正看到我在哪里违反了合同。所以关闭异常似乎是错误的解决方案。

【问题讨论】:

  • 你重写了equals() 方法吗?
  • @Ray Toal:距离是双倍的。
  • 你不需要覆盖equals(),只是想知道它和compareTo() 的行为可能导致违反一般合同。例如,.equals() 返回 true,但 compareTo() 返回非零。
  • 没有 NaN,你应该很好:ideone.com/u1HD9。使用 NaN,ideone 的 Java 7 仍然适用于我:ideone.com/OsixW
  • 完整堆栈跟踪:ideone.com/7GV37 匹配是一个整数,距离是一个双精度数,使用 2 个 lat/lng 对计算。这会导致 NaN 吗?

标签: java sorting collections comparable


【解决方案1】:

既然您说distance 是双精度数,您可能遇到与此处所述相同的问题:

Java error: "Comparison method violates its general contract!"

也许:

public int compareTo(Object o) {
    Match m = (Match)o;
    int diff = m.matches - matches;
    if (diff == 0) {
        return Double.compare(distance, m.distance);
    } else {
        return diff;
    }
}

但是理想情况下,您应该使用内置的比较方法,如下所述。 上面的代码是一个“最小改动”的例子,说明了关键问题。

使用现有的比较方法

另外,正如@fabian-barney 在他的回答中所说,您应该避免采用直接差异,而是使用内置的比较方法。所以你应该有类似的东西:

public int compareTo(Object o) {
    Match m = (Match) o;
    return m.matches == matches ? Double.compare(m.distance, distance) : Integer.compare(m.matches, matches);
}

这样,Double.compare 将为您处理 NaN 值。对于任何数字x(NaN 除外)Double.compare(x, Double.NaN) == -1 将返回 true(即 NaN 被认为大于任何其他数字)。

请注意,您可以将==ints 一起使用,但使用double 会更复杂,因为Double.NaN != Double.NaN。但是,new Double(Double.NaN).equals(Double.NaN) 是真的。请参阅Why is Java's Double.compare(double, double) implemented the way it is? 进行精彩讨论。

合同违约:

要查看为什么您的原始实现可能会在您有 NaN 时违反合同的示例,请参阅Java compareTo documentation。我们有:

最后,实现者必须确保 x.compareTo(y)==0 意味着 sgn(x.compareTo(z)) == sgn(y.compareTo(z)),对于所有 z。

假设你有x = NaNy = 5z = 6,那么:

  1. x.compareTo(y) == 0(因为NaN &gt; 5NaN &lt; 5是假的)
  2. x.compareTo(z) == 0(同理)
  3. y.compareTo(z) == -1 (y

所以 2 和 3 (+sgn) 不等于所要求的。

【讨论】:

  • 确实,这确实有效,但我不确定为什么 - 或者我的列表中哪里有任何 NaN。
  • 我已经用一个例子更新了我的答案,如果存在 NaN,合同就会被破坏。我不能说它们会出现在您的代码中的什么位置,或者这绝对是问题所在:(
  • 你是对的 - 距离的计算值中有 NaN:ideone.com/R9lTB
  • 很高兴它成功了!如果您还没有,请不要忘记查看@fabian-barney 关于正确使用比较方法的答案。我会完全更新我的答案以将其考虑在内,但我宁愿确保他得到应得的选票:)。
  • @MGwynne 随意添加。我相信它会进一步改善您已经非常好的答案。从一开始就+1 顺便说一句
【解决方案2】:

不要在compareTo(...) 方法中返回diff。这不适用于所有值。比如Integer.MAX_VALUE - Integer.MIN_VALUE的结果是否定的。

将其重写为:

public int compareTo(Object o) {
    Match m = (Match) o;
    return m.matches == matches ? Double.compare(m.distance, distance) : Integer.compare(m.matches, matches);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-07-16
    • 1970-01-01
    • 2019-03-19
    • 1970-01-01
    • 2019-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多