Java错误:比较方法违反了其一般合同

时间:2012-07-11 21:20:15

标签: java compare migration java-7 comparator

我看到很多关于此的问题,并试图解决这个问题,但经过一个小时的谷歌搜索和大量的试验和错误,我仍然无法解决它。我希望你们中的一些人能够解决这个问题。

这就是我得到的:

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

有什么想法吗?

10 个答案:

答案 0 :(得分:76)

异常消息实际上非常具有描述性。它提到的合同是传递性:如果A > BB > C那么任何ABC:{{1 }}。我用纸和笔检查了它,你的代码似乎有几个漏洞:

A > C

如果if (card1.getRarity() < card2.getRarity()) { return 1; ,则不会返回-1


card1.getRarity() > card2.getRarity()

如果ID不相等,则返回if (card1.getId() == card2.getId()) { //... } return -1; 。您应该返回-1-1,具体取决于哪个ID更大。


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

1

答案 1 :(得分:29)

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

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

答案 2 :(得分:28)

它还与JDK版本有关。 如果它在JDK6中运行良好,可能会在您描述的JDK 7中出现问题,因为jdk 7中的实现方法已经改变。

看看这个:

描述:java.util.Arrays.sort和({间接)java.util.Collections.sort使用的排序算法已被替换。如果新排序实施检测到违反IllegalArgumentException合同的Comparable,则可能会抛出Comparable。以前的实现默默地忽略了这种情况。如果需要先前的行为,则可以使用新的系统属性java.util.Arrays.useLegacyMergeSort来恢复先前的mergesort行为。

我不知道具体原因。但是,如果在使用sort之前添加代码。没关系。

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

答案 3 :(得分: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()恰好为真,cardType > item.getCardType()也是如此。你又回来了。

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

答案 4 :(得分: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;

    }
}

答案 5 :(得分:1)

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

答案 6 :(得分:0)

我不得不排序几个标准(日期,如果相同的日期;其他事情......)。使用旧版Java的Eclipse工作原理在Android上没有用处:比较方法违反合同......

在阅读StackOverflow之后,我写了一个单独的函数,如果日期相同,我会从compare()调用。此函数根据条件计算优先级,并返回-1,0或1以进行比较()。它似乎现在工作。

答案 7 :(得分: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);

修正错误。

答案 8 :(得分:0)

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

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

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

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

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

答案 9 :(得分:0)

我遇到了类似的问题,我试图对名为n x 2 2D array的{​​{1}}进行排序,该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;
        });