“比较法违反了其一般合同!”


187

有人可以简单地向我解释一下,为什么这段代码会引发异常,“比较方法违反了它的一般约定!”,我该如何解决?

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}

1
异常的名称和类别是什么?是IllegalArgumentException吗?如果我不得不猜测,我会认为你应该做s1.getParent().equals(s2)而不是做s1.getParent() == s2
Freiheit

以及抛出的异常。
马修·法威尔

2
我对Java或Java比较API不太了解,但是这种比较方法似乎完全错误。假设s1是的父项s2,而s2不是的父项s1。然后compareParents(s1, s2)0,但是compareParents(s2, s1)1。那没有道理。(此外,它不是可传递的,就像下面提到的aix一样。)
mqp 2011年

4
似乎只有通过特定的库来产生这个错误cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/...
彼得Lawrey

在Java中,您可以使用equals(返回布尔值)或compareTo(返回-1、0或+1)。在您的Foo类中重写此函数,然后,您可以检查s1.getParent()。equals(s2)...
Mualig 2011年

Answers:


261

您的比较器不是可传递的。

A是的父B,并B成为母公司C。既然A > BB > C,那么一定是这样A > C。但是,如果在A和上调用比较器C,它将返回零,即A == C。这违反了合同,因此引发异常。

该库可以很好地检测到这一点并让您知道,而不是行为不稳定。

满足传递性要求的一种方法compareParents()是遍历整个getParent()链,而不是仅查看直接祖先。


3
引入Java 7的java.util.Arrays.sort stackoverflow.com/questions/7849539/…–
leonbloy

46
图书馆检测到的事实真棒。太阳有人应该扔掉一个巨人。不客气
Qix-蒙尼卡(Monica)失误了

您能否概括一下这个答案,以使该问题作为参考文章更有用?
伯恩哈德·巴克

1
@Qix -像我爱孙,这是在Java 7中添加了甲骨文的旗帜下
isapir

1
@isapir该死!接得好。
Qix-蒙尼卡(Monica)

38

只是因为这是我在搜索此错误时所得到的,所以我的问题是

if (value < other.value)
  return -1;
else if (value >= other.value)
  return 1;
else
  return 0;

value >= other.value应(显然)实际上是value > other.value让你可以实际上等于对象返回0。


7
我必须补充一点,如果您的任何一个value是NaN(如果value是a doublefloat),它也会失败。
Matthieu 2014年

22

违反合同通常意味着比较对象在比较对象时未提供正确或一致的值。例如,您可能要执行字符串比较,并强制空字符串以以下形式排序:

if ( one.length() == 0 ) {
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

但这忽略了第一个和第二个都为空的情况,在这种情况下,返回了错误的值(1而不是0表示匹配),比较器将其报告为违规。它应该写为:

if ( one.length() == 0 ) {
    if ( two.length() == 0 ) {
        return 0;               // BOth empty - so indicate
    }
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

13

即使您的compareTo在理论上具有传递性,有时细微的错误也会使事情变得混乱……例如浮点算术错误。它发生在我身上。这是我的代码:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    else
        return 0;

}   

传递属性显然成立,但是由于某种原因,我得到了IllegalArgumentException。事实证明,由于浮点运算中的微小错误,舍入错误导致传递属性在不应该发生的地方中断!因此,我重新编写了代码,以考虑非常小的差异0,并且它的工作原理是:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if ((this.tfidf - compareTfidf.tfidf) < .000000001)
        return 0;
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    return 0;
}   

2
这很有帮助!我的代码在逻辑上还可以,但是由于精度问题而出现了错误。
JSong

6

在我们的案例中,出现此错误是因为我们不小心翻转了s1和s2的比较顺序。所以要当心。显然,它比以下方法更为复杂,但这只是一个例证:

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;

3

Java不会严格检查一致性,只有在遇到严重问题时才会通知您。同样,它也不能为您提供很多错误信息。

我对排序器中发生的事情感到困惑,并进行了严格的一致性检查器,也许这将对您有所帮助:

/**
 * @param dailyReports
 * @param comparator
 */
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
  final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();

  iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
    /**
     * @param o1
     * @param o2
     */
    @Override
    public void pair(T o1, T o2) {
      final int diff = comparator.compare(o1, o2);
      if (diff < Compare.EQUAL) {
        checkConsistency(objectMapSmallerOnes, o1, o2);
        getListSafely(objectMapSmallerOnes, o2).add(o1);
      } else if (Compare.EQUAL < diff) {
        checkConsistency(objectMapSmallerOnes, o2, o1);
        getListSafely(objectMapSmallerOnes, o1).add(o2);
      } else {
        throw new IllegalStateException("Equals not expected?");
      }
    }
  });
}

/**
 * @param objectMapSmallerOnes
 * @param o1
 * @param o2
 */
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
  final List<T> smallerThan = objectMapSmallerOnes.get(o1);

  if (smallerThan != null) {
    for (final T o : smallerThan) {
      if (o == o2) {
        throw new IllegalStateException(o2 + "  cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
      }
      checkConsistency(objectMapSmallerOnes, o, o2);
    }
  }
}

/**
 * @param keyMapValues 
 * @param key 
 * @param <Key> 
 * @param <Value> 
 * @return List<Value>
 */ 
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
  List<Value> values = keyMapValues.get(key);

  if (values == null) {
    keyMapValues.put(key, values = new LinkedList<Value>());
  }

  return values;
}

/**
 * @author Oku
 *
 * @param <T>
 */
public interface IPairIteratorCallback<T> {
  /**
   * @param o1
   * @param o2
   */
  void pair(T o1, T o2);
}

/**
 * 
 * Iterates through each distinct unordered pair formed by the elements of a given iterator
 *
 * @param it
 * @param callback
 */
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
  List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {

    @Override
    public Iterator<T> iterator() {
      return it;
    }

  });

  for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
    for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
      callback.pair(list.get(outerIndex), list.get(innerIndex));
    }
  }
}

只需使用参数列表和比较器调用checkConsitency方法。
马丁

您的代码无法编译。类CompareConvert(和潜在的其他人)没有定义。请使用一个完整的示例更新代码片段。
吉利2016年

您应该修正错字checkConsi(s)tency并删除所有多余的@param声明,以使代码更具可读性。
罗兰·伊利格

3

就我而言,我正在执行以下操作:

if (a.someField == null) {
    return 1;
}

if (b.someField == null) {
    return -1;
}

if (a.someField.equals(b.someField)) {
    return a.someOtherField.compareTo(b.someOtherField);
}

return a.someField.compareTo(b.someField);

我忘记检查的是当a.someField和b.someField均为null时。


3

我已经在一段代码中看到了这种情况,该代码中经常执行空值检查:

if(( A==null ) && ( B==null )
  return +1;//WRONG: two null values should return 0!!!


1

编辑虚拟机配置对我有用。

-Djava.util.Arrays.useLegacyMergeSort=true

请再次确认我为您提供格式帮助的尝试没有破坏任何内容。我不确定-拟议解决方案的开始。也许您想要的是类似单项的项目清单。
Yunnosch

2
还请说明这如何帮助解决所描述的问题。当前,它实际上是仅代码的答案。
Yunnosch

0

您不能像这样比较对象数据:s1.getParent() == s2-这将比较对象引用。您应该重写equals functionFoo类,然后像这样比较它们s1.getParent().equals(s2)


不,实际上我认为OP试图对某种列表进行排序,并希望实际比较引用。
爱德华·福克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.