web-dev-qa-db-ja.com

Javaエラー:比較メソッドが一般契約に違反しています

私はこれについて多くの質問を見て、問題を解決しようとしましたが、1時間のグーグルと多くの試行錯誤の後、私はまだそれを修正できません。皆さんの何人かが問題をキャッチすることを願っています。

これは私が得るものです:

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

何か案が?

66
Lakatos Gyula

例外メッセージは実際にはかなり説明的です。それが言及している契約は、推移性:if A > B and B > C then for AB and CA > C。紙と鉛筆で確認しましたが、コードにはいくつかの穴が開いているようです:

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

card1.getRarity() > card2.getRarity()の場合、-1を返しません。


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

IDが等しくない場合、-1を返します。どのIDが大きいかに応じて、-1または1を返す必要があります。


これを見てください。はるかに読みやすいだけでなく、実際に機能するはずです。

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!
79

次のクラスを使用して、コンパレータの推移性バグを特定できます。

/**
 * @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)を呼び出すだけです。

30
Gili

また、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");
29
Justin Civi

次の場合を考えてください:

まず、o1.compareTo(o2)が呼び出されます。 card1.getSet() == card2.getSet()はたまたまcard1.getRarity() < card2.getRarity()であるため、1を返します。

次に、o2.compareTo(o1)が呼び出されます。繰り返しますが、card1.getSet() == card2.getSet()はtrueです。次に、次のelseにスキップすると、card1.getId() == card2.getId()がtrueになり、cardType > item.getCardType()もtrueになります。再び1を返します。

それから、o1 > o2、およびo2 > o1。契約を破った。

6
eran
        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;

    }
}
2
Joe

私はいくつかの基準(日付、および同じ日付の場合、他のもの...)でソートする必要がありました。古いバージョンのJavaでEclipseで機能していたものがAndroidで機能しなくなりました:比較方法が契約に違反しています...

StackOverflowを読んだ後、日付が同じ場合にcompare()から呼び出す別の関数を作成しました。この関数は、基準に従って優先順位を計算し、compare()に-1、0、または1を返します。今は機能しているようです。

0
Jean Burkhardt

次の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
pmkent