为什么我的比较方法会抛出异常——比较方法违反了其通用约定!

14

为什么这段代码会

public class SponsoredComparator implements Comparator<SRE> {

    public boolean equals(SRE arg0, SRE arg1){
        return arg0.getSponsored()==arg1.getSponsored();
    }

    public int compare(SRE object1, SRE object2) {
        Log.d("SponsoredComparator","object1.getName() == "+ object1.getName());
        Log.d("SponsoredComparator","object1.getSponsored() == "+ object1.getSponsored());
        Log.d("SponsoredComparator","object2.getName() == "+ object2.getName());
        Log.d("SponsoredComparator","object2.getSponsored() == "+ object2.getSponsored());
        Log.d("SponsoredComparator","compare return == "+ (object1.getSponsored() && object2.getSponsored() ? 0 : object1.getSponsored() ? -1 : 1));
        return object1.getSponsored() && object2.getSponsored() ? 0 : object1.getSponsored() ? -1 : 1;
    }
}

抛出此异常:ERROR/AndroidRuntime(244): java.lang.IllegalArgumentException: Comparison method violates its general contract!
ERROR/AndroidRuntime(4446): at java.util.TimSort.mergeLo(TimSort.java:743)

sre.getSponsored() 方法返回一个布尔值。

谢谢。


看一下这个 https://stackoverflow.com/questions/68778282/fatal-exception-java-lang-illegalargumentexception-comparison-method-violates 这解决了我的问题。 - Parteek Singh Bedi
7个回答

27
我猜测问题出现在两个值都没有被赞助的时候。无论你如何调用它,它都会返回1。
x1.compare(x2) == 1

x2.compare(x1) == 1

这是无效的。

我建议你将此更改为:

object1.getSponsored() && object2.getSponsored()

to

object1.getSponsored() == object2.getSponsored()

两个地方都需要重复使用此代码,我可能会将其提取为一个方法,并在某个地方使用这个签名:

public static int compare(boolean x, boolean y)

然后像这样调用它:

public int compare(SRE object1, SRE object2) {
    return BooleanHelper.compare(object1.getSponsored(), object2.getSponsored());
}

我个人认为这将使代码更清晰易懂。

这并不直接与您遇到的问题有关,但是您需要担心空值吗? - Clockwork-Muse

20
我假设你正在使用JDK 7。请检查以下网址:
从{{link1:http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#source}}
区域:API:实用程序
简介:更新了ArraysCollections的排序行为可能会抛出IllegalArgumentException 描述:由java.util.Arrays.sort和(间接地)java.util.Collections.sort使用的排序算法已被替换。新的排序实现可能会抛出IllegalArgumentException,如果它检测到违反Comparable合同的Comparable。以前的实现会默默地忽略这种情况。如果需要以前的行为,您可以使用新的系统属性java.util.Arrays.useLegacyMergeSort来恢复以前的合并排序行为。
不兼容性的性质:行为
RFE:6804124
更详细的信息,请参阅错误数据库此处的参考链接

5
你的猜测基本正确。OP使用的是Android(而不是JDK 7),但你提供的参考仍然正确,因为JDK 7和Android都使用TimSort算法。如果与违反Comparable合同的Comparable一起使用,则会抛出IllegalArgumentException异常。 - Idolon

10

equals() 和 compareTo() 的约定是当 equals() 返回 true 时,compareTo() 应该返回 0,当 equals() 返回 false 时,compareTo() 应该返回 -1 或 +1。

顺便说一句:我假设您的 compare() 方法不会被经常调用,因为调试信息会占用相当多的 CPU 和内存。


3
这些调试信息只是用来帮助修复这个错误的。 - lost baby
根据文档,“强烈建议但不是必须的,(x.compareTo(y)==0) == (x.equals(y))”。而且,这个问题的异常并不是由于违反equals方法的契约而产生的。我在这个旧答案上写下了这个评论,因为它让我得出了错误的结论,即这个异常中的违约与equals方法有关。 - Artur Skrzydło
@Peter Lawrey 这是我找到的第一份清晰解释“合同”是什么的信息。谢谢你。但是,鉴于equals必须返回与compareTo相对应的值,我认为对于所有实现compareTo的用户来说,最好的解决方案是还要覆盖equals并在该方法中简单地调用compareTo,如果结果为0,则返回true,如果不为0,则返回false。唯一需要注意的是,在compareTo中不能调用equals。 - zreptil
@zreptil 使用compareTo存在一些问题; a) 有时候两个值可能相等,但是compareTo不为0,例如-0.0和0.0以及BigDecimal值。另一个问题是,两个值可能没有任何合理的高低概念,也不能检查它们是否相等。 - Peter Lawrey
@Peter Lawrey 我认为可以毫无问题地使用compareTo。在Java团队决定违反compareTo和equals之间的契约必须抛出异常之前,它已经在我的项目中使用了多年,但这并没有真正澄清出现了什么问题。我很少使用compareTo,但它是一种方便的方法,用于比较类的两个实例以确定哪一个必须在另一个之前进行排序。在了解了合同细节后,修复该异常的bug非常容易,并且像魅力一样工作。 - zreptil

1
我同意所有答案,特别是Jon的答案,但还想补充一点:我们应该在比较方法中始终检查null安全性,以确保我们的方法永远不会出错。这是编程中良好的习惯,始终进行空值检查。如需更多信息,请查看此处

0
也许你只是比较 Collections.sort 中的 NaN 值所遇到的问题……我在正确实现 compare(obj1, obj2) 方法后仍然遇到了这个异常!检查一下吧!

0
今天在一个Web应用程序中,我遇到了同样的问题。四个调用同时对同一个数组进行排序,实际上相互干扰,导致出现了问题。

0
我的解决方案:当我想要对数字进行排序,而数组元素为空时,我将其替换为0,这样错误就消失了。需要注意的是,二维数组中每行的大小必须相同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接