“比较法违反了其总契约!”

谁能简单地解释一下,为什么这段代码抛出一个异常,“比较方法违反了它的一般契约!”,以及我该如何修复它?

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

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

AB的父类,BC的父类。既然A > BB > C,那么A > C必须是这样。然而,如果你的比较器在AC上被调用,它将返回零,即A == C。这违反了契约,因此抛出异常。

库能够检测到这一点并让您知道,而不是表现得不稳定,这是相当不错的。

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

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

只是因为这是我得到的当我谷歌这个错误,我的问题是我有

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

value >= other.value(显然)实际上应该是value > other.value,这样你就可以在相同的对象中返回0。

我曾在一段代码中看到过这种情况,其中经常执行空值检查:

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

违反合同通常是指比较者在比较对象时没有提供正确或一致的值。例如,你可能想要执行一个字符串比较,并强制空字符串排序到最后:

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和2都为空的情况——在这种情况下,返回了错误的值(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 );

从严格意义上讲,Java并不检查一致性,只是在遇到严重问题时才通知您。此外,它也没有从错误中提供太多信息。

我对在我的分类器中发生的事情感到困惑,并做了一个严格的consistencyChecker,也许这将帮助你:

/**
* @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));
}
}
}

在我们的例子中,我们得到这个错误是因为我们不小心颠倒了s1和s2比较的顺序。所以要小心。它显然比下面的要复杂得多,但这是一个例子:

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

即使你的compareTo在理论上具有可传递性,有时细微的bug会把事情搞糟……如浮点运算错误。这件事发生在我身上。这是我的代码:

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;
}

以我为例,我做了如下事情:

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都为空时。

如果compareParents(s1, s2) == -1,则期望compareParents(s2, s1) == 1。对于你的代码,这并不总是正确的。

特别是s1.getParent() == s2 && s2.getParent() == s1。 这只是可能出现的问题之一。

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

-Djava.util.Arrays.useLegacyMergeSort=true
在我的例子中,它是一个无限排序。 也就是说,首先这条线根据条件向上移动,然后这条线向下移动到相同的位置。 我在最后添加了一个条件,明确地建立了行顺序

我也遇到过同样的问题,但我解决了。

//This this your code


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

违例是将不同的事物相互比较。

//acceptable
compare between s1.getParent() and s2.getParent()
//acceptable
compare between s1 and s2
//NOT acceptable
compare between s1 and s2.getParent()
//NOT acceptable
compare between s1.getParent() and s2

在我的代码中,我想通过地址的协调来排序。在比较器中,我错误地比较了X和Y,而不是X和X。

//My code:
private void sortBasedOnX(){
//addresses is a list of addresses where each address has X and Y
addresses.sort((o1, o2) -> {


String a = o1.getAddress().getX();
String b = o2.getAddress().getY(); //<-- this is supposed to be getX


return Integer.parseInt(a)-Integer.parseInt(b);
});
}
//acceptable
compare between o1.getAddress().getX() and o1.getAddress().getX()
//acceptable
compare between o1.getAddress().getY() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress().getY()
//NOT acceptable
compare between o1.getAddress().getX() and o1.getAddress()
//NOT acceptable
compare between o1.getAddress().getX() and o1