Java错误:比较方法违反其一般约定


84

我看到了很多与此有关的问题,并尝试解决了该问题,但是经过一个小时的搜索和大量的试验和错误后,我仍然无法解决。我希望你们中的一些人能抓住问题。

这是我得到的:

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
    at java.util.Arrays.sort(Arrays.java:472)
    at java.util.Collections.sort(Collections.java:155)
    ...

这是我的比较器:

@Override
public int compareTo(Object o) {
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());

    if (card1.getSet() < card2.getSet()) {
        return -1;
    } else {
        if (card1.getSet() == card2.getSet()) {
            if (card1.getRarity() < card2.getRarity()) {
                return 1;
            } else {
                if (card1.getId() == card2.getId()) {
                    if (cardType > item.getCardType()) {
                        return 1;
                    } else {
                        if (cardType == item.getCardType()) {
                            return 0;
                        }
                        return -1;
                    }
                }
                return -1;
            }
        }
        return 1;
    }
}

任何的想法?


哪行代码导致引发此异常?ComparableTimSort.java的835行和453行是什么?
气垫船充满鳗鱼,2012年

在我看来,您应该将此方法委托给Card类:return card1.compareTo(card2)并在此实现此逻辑。
亨特·麦克米伦

2
@HovercraftFullOfEels这是Oracle的类,不是我写的。它引发了这一行的异常。该方法很长,看起来很难理解。
Lakatos Gyula 2012年

@HunterMcMillen我不能那样做。卡仅包含卡的静态数据(名称,文本等),而此类包含一些变化的变量,如类型和金额。
Lakatos Gyula 2012年

1
看完《干净代码》这本书后,我也不知道。
Lakatos Gyula 2014年

Answers:


97

异常消息实际上是描述性的。这里所指的合同是传递:如果A > BB > C那么对于任意的ABCA > C。我用纸和铅笔检查了一下,您的代码似乎有几个孔:

if (card1.getRarity() < card2.getRarity()) {
  return 1;

-1如果您不返回card1.getRarity() > card2.getRarity()


if (card1.getId() == card2.getId()) {
  //...
}
return -1;

-1如果id不相等,则返回。您应该返回-11根据哪个ID更大。


看看这个。除了更具可读性之外,我认为它实际上应该可以工作:

if (card1.getSet() > card2.getSet()) {
    return 1;
}
if (card1.getSet() < card2.getSet()) {
    return -1;
};
if (card1.getRarity() < card2.getRarity()) {
    return 1;
}
if (card1.getRarity() > card2.getRarity()) {
    return -1;
}
if (card1.getId() > card2.getId()) {
    return 1;
}
if (card1.getId() < card2.getId()) {
    return -1;
}
return cardType - item.getCardType();  //watch out for overflow!

15
其实,你给出的例子并不违反传递,但规则SGN(比较(X,Y))== -sgn(对比(Y,X))项记载 docs.oracle.com/javase/7/docs/ api / java / util / ...-从数学角度讲是反对称的。
汉斯·彼得·斯特尔2014年

1
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;如果有人在意,我建议使用更快的速度。
maaartinus 2014年

我遇到了这个问题,这是我的比较器中的一个对称问题。很难诊断的部分是,我的弱点是第一个字段(布尔字段检查,不是正确的比较),它没有检查两个字段是否正确。但是,只有在我添加了随后的下级比较(该顺序一定会扰乱排序顺序)之后,才公开此内容。
Thomas W

1
@maaartinus感谢您的建议!对我来说是完美的:)
Sonhja

45

您可以使用以下类在比较器中查明传递性错误:

/**
 * @author Gili Tzabari
 */
public final class Comparators
{
    /**
     * Verify that a comparator is transitive.
     *
     * @param <T>        the type being compared
     * @param comparator the comparator to test
     * @param elements   the elements to test against
     * @throws AssertionError if the comparator is not transitive
     */
    public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
    {
        for (T first: elements)
        {
            for (T second: elements)
            {
                int result1 = comparator.compare(first, second);
                int result2 = comparator.compare(second, first);
                if (result1 != -result2)
                {
                    // Uncomment the following line to step through the failed case
                    //comparator.compare(first, second);
                    throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
                        " but swapping the parameters returns " + result2);
                }
            }
        }
        for (T first: elements)
        {
            for (T second: elements)
            {
                int firstGreaterThanSecond = comparator.compare(first, second);
                if (firstGreaterThanSecond <= 0)
                    continue;
                for (T third: elements)
                {
                    int secondGreaterThanThird = comparator.compare(second, third);
                    if (secondGreaterThanThird <= 0)
                        continue;
                    int firstGreaterThanThird = comparator.compare(first, third);
                    if (firstGreaterThanThird <= 0)
                    {
                        // Uncomment the following line to step through the failed case
                        //comparator.compare(first, third);
                        throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
                            "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
                            firstGreaterThanThird);
                    }
                }
            }
        }
    }

    /**
     * Prevent construction.
     */
    private Comparators()
    {
    }
}

只需Comparators.verifyTransitivity(myComparator, myCollection)在失败的代码前面调用即可。


3
此代码验证compare(a,b)=-compare(b,c)。它不检查是否compare(a,b)> 0 && compare(b,c)> 0,然后compare(a,c)> 0
Olaf Achthoven

3
我真的发现您的课程非常非常有用。谢谢。
克里戈尔

谢谢,我不得不回来回答这个问题,因为此类在调试比较器时非常有用,不仅用于此处提到的目的,因为它也可以更改为测试其他情况!
Jaco-Ben Vosloo

36

它还与JDK的版本有关。如果它在JDK6中表现良好,则可能是您所描述的JDK 7中存在问题,因为jdk 7中的实现方法已更改。

看这个:

说明:java.util.Arrays.sort和(间接)使用的排序算法java.util.Collections.sort已被替换。如果新排序实现IllegalArgumentException检测Comparable到违反Comparable合同的协议,则可能引发。先前的实现默默地忽略了这种情况。如果需要以前的行为,则可以使用新的系统属性java.util.Arrays.useLegacyMergeSort来还原以前的mergesort行为。

我不知道确切原因。但是,如果在使用排序之前添加了代码。一切都会安好的。

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

1
问题出在我用来比较事物的逻辑中。我会对此表示赞同,希望它可以帮助正在寻找此类问题的人。
Lakatos Gyula

10
在修复比较器之前,仅应将其用作使事情再次起作用的快速而丑陋的解决方法。如果发生异常,则意味着您的比较器存在错误,排序顺序将很奇怪。您必须考虑docs.oracle.com/javase/7/docs/api/java/util/…中给出的所有规则。
汉斯·彼得·斯特尔2014年

如果我要的是“怪异”怎么办?(a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}我知道Collections.shuffle存在,但我不喜欢过度保护。
杰弗里·凯夫

我有一个旧的应用程序,使用JDK8出现此错误。使用JDK6可以正常工作,所以我简单地降级到6。
Stefano Scarpanti

6

考虑以下情况:

首先o1.compareTo(o2)被称为。card1.getSet() == card2.getSet()碰巧是真的,所以card1.getRarity() < card2.getRarity(),所以您返回1。

然后,o2.compareTo(o1)被称为。再次,card1.getSet() == card2.getSet()是真的。然后,跳至以下内容else,然后card1.getId() == card2.getId()碰巧是true,也是如此cardType > item.getCardType()。您再次返回1。

由此o1 > o2,和o2 > o1。你违反了合同。


2
        if (card1.getRarity() < card2.getRarity()) {
            return 1;

但是,如果card2.getRarity()小于,则card1.getRarity()可能不会返回-1

您同样会错过其他情况。我会这样做,您可以根据自己的意图进行更改:

public int compareTo(Object o) {    
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());
    int comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }
    comp=card1.getRarity() - card2.getRarity();
    if (comp!=0){
        return comp;
    }
    comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getId() - card2.getId();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getCardType() - card2.getCardType();

    return comp;

    }
}

1

这也可能是OpenJDK错误...(不是这种情况,但是是相同的错误)

如果像我这样的人偶然发现了关于

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

那么它也可能是Java版本中的错误。从现在开始,我已经在某些应用程序中运行了compareTo。但是突然间它停止工作,并在完成所有比较后抛出错误(我在返回“ 0”之前比较了6个属性)。

现在,我刚刚找到了OpenJDK的这个Bugreport:


1

我有相同的症状。对我来说,事实证明,在Stream中进行排序时,另一个线程正在修改比较的对象。为了解决该问题,我将对象映射到不可变的临时对象,将Stream收集到一个临时Collection中,并对其进行排序。


0

我必须根据几个条件进行排序(日期,如果是同一日期,还需要其他东西...)。在使用旧版Java的Eclipse上运行的东西,在Android上不再起作用:比较方法违反合同...

阅读StackOverflow之后,我写了一个单独的函数,如果日期相同,则从compare()调用。此函数根据条件计算优先级,然后将-1、0或1返回给compare()。现在似乎可以正常工作了。


0

我在类似以下的类中遇到了相同的错误StockPickBean。从此代码调用:

List<StockPickBean> beansListcatMap.getValue();
beansList.sort(StockPickBean.Comparators.VALUE);

public class StockPickBean implements Comparable<StockPickBean> {
    private double value;
    public double getValue() { return value; }
    public void setValue(double value) { this.value = value; }

    @Override
    public int compareTo(StockPickBean view) {
        return Comparators.VALUE.compare(this,view); //return 
        Comparators.SYMBOL.compare(this,view);
    }

    public static class Comparators {
        public static Comparator<StockPickBean> VALUE = (val1, val2) -> 
(int) 
         (val1.value - val2.value);
    }
}

得到相同的错误后:

java.lang.IllegalArgumentException:比较方法违反了其一般约定!

我更改了这一行:

public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) 
         (val1.value - val2.value);

至:

public static Comparator<StockPickBean> VALUE = (StockPickBean spb1, 
StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);

可以修复错误。


0

我遇到了一个类似的问题,我试图对一个n x 2 2D arraynamed进行排序contests,这是一个2D的简单整数数组。这在大多数情况下都有效,但是对一个输入抛出了运行时错误:

Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            } else return -1;
        });

错误:-

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.TimSort.mergeHi(TimSort.java:903)
    at java.base/java.util.TimSort.mergeAt(TimSort.java:520)
    at java.base/java.util.TimSort.mergeForceCollapse(TimSort.java:461)
    at java.base/java.util.TimSort.sort(TimSort.java:254)
    at java.base/java.util.Arrays.sort(Arrays.java:1441)
    at com.hackerrank.Solution.luckBalance(Solution.java:15)
    at com.hackerrank.Solution.main(Solution.java:49)

查看上面的答案,我尝试为添加一个条件,equals但不知道为什么,但是它有效。希望我们必须明确指定在所有情况下(大于,等于和小于)应返回的内容:

        Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            }
            if(row1[0] == row2[0]) return 0;
            return -1;
        });
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.