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

Lak*_*ula 68 java migration compare comparator java-7

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

这就是我得到的:

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)
    ...
Run Code Online (Sandbox Code Playgroud)

这是我的比较器:

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

任何的想法?

Tom*_*icz 82

异常消息实际上非常具有描述性.这里所指的合同是传递:如果A > BB > C那么对于任意的A,BC:A > C.我用纸和笔检查了它,你的代码似乎有几个漏洞:

if (card1.getRarity() < card2.getRarity()) {
  return 1;
Run Code Online (Sandbox Code Playgroud)

你不回-1,如果card1.getRarity() > card2.getRarity().


if (card1.getId() == card2.getId()) {
  //...
}
return -1;
Run Code Online (Sandbox Code Playgroud)

-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!
Run Code Online (Sandbox Code Playgroud)

  • 实际上,您给出的示例并未违反传递性,但规则sgn(compare(x,y))== -sgn(compare(y,x))如http://docs.oracle.com/javase/中所述7/docs/api/java/util/Comparator.html #comparison(T,%20T) - 这是数学术语中的反对称性. (15认同)

Gil*_*ili 34

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

/**
 * @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()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 此代码验证compare(a,b)=-compare(b,c)。它不检查是否compare(a,b)&gt; 0 &amp;&amp; compare(b,c)&gt; 0则compare(a,c)&gt; 0 (3认同)
  • 我真的觉得你的课非常非常有用.谢谢. (3认同)

Jus*_*ivi 30

它还与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");
Run Code Online (Sandbox Code Playgroud)

  • 这只应该用作一种快速而丑陋的解决方法,以便在您修复比较器之前再次使用.如果异常发生,则意味着比较器有问题且排序顺序很奇怪.您必须考虑http://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html#compare(T,%20T)中给出的所有规则. (8认同)

era*_*ran 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().你再次返回1.

从那起o1 > o2,和o2 > o1.你违反了合同.