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


86

私はこれについて多くの質問を見て問題を解決しようとしましたが、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;
    }
}

何か案が?


この例外がスローされる原因となるコード行は何ですか?ComparableTimSort.javaの835行目と453行目は何ですか?
Hovercraft Full Of Eels 2012

このメソッドをCardクラスに委任する必要があるように思われます:return card1.compareTo(card2)そしてそこでこのロジックを実装します。
ハンターマクミレン2012

2
@HovercraftFullOfEelsこれはOracleのクラスであり、私が作成したものではありません。それはその行の例外です。この方法は非常に長く、理解しにくいように見えます。
Lakatos Gyula 2012

@HunterMcMillenそれはできません。カードにはカードの静的データ(名前、テキストなど)のみが含まれますが、このクラスにはタイプや金額などの変更変数が含まれます。
Lakatos Gyula 2012

1
クリーンコードの本を読んだ後、私も知りません。
Lakatos Gyula 2014

回答:


99

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

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

あなたは返さない-1場合card1.getRarity() > card2.getRarity()


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

-1IDが等しくない場合に戻ります。戻る-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
実際、あなたが与えた例は推移性に違反していませんが、docs.oracle.com / javase / 7 / docs /に記載されているルールsgn(compare(x、y))== -sgn(compare(y、x)) api / java / util /… -これは数学的には反対称です。
ハンス・ペーター・ストー

1
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;誰かが気にかけているなら、もっとスピードを上げるために使うことをお勧めします。
maaartinus 2014

私はこれに遭遇しました、そしてそれは私のコンパレータの対称性の問題でした。診断するのが難しい部分は、私の弱点が最初のフィールド(ブールフィールドチェックであり、適切な比較ではない)であり、両方が真に正しいかどうかをチェックしなかったことです。ただし、これは、並べ替え順序を混乱させたに違いない後続の下位比較を追加した後でのみ公開されました。
トーマスW

1
@maaartinusあなたの提案に感謝します!それは私にとって完璧でした:)
Sonhja 2017年

46

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

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

3
あなたのクラスはとてもとても役に立ちました。ありがとう。
クリゴア2016

このクラスは、他の状況をテストするために変更できるため、ここで説明した目的だけでなく、コンパレータをデバッグするときに非常に役立つため、戻って回答に賛成する必要がありました。
Jaco-Ben Vosloo

37

また、JDKのバージョンとも関係があります。JDK6でうまくいく場合は、jdk 7の実装方法が変更されているため、説明したJDK7で問題が発生する可能性があります。

これを見てください:

説明:java.util.Arrays.sortおよび(間接的に)によって使用されるソートアルゴリズムjava.util.Collections.sortは置き換えられました。新しいソート実装は、コントラクトに違反するをIllegalArgumentException検出した場合にをスローする可能性があります。以前の実装では、このような状況を黙って無視していました。以前の動作が必要な場合は、新しいシステムプロパティ、、を使用して、以前のマージソートの動作を復元できます。ComparableComparablejava.util.Arrays.useLegacyMergeSort

正確な理由はわかりません。ただし、sortを使用する前にコードを追加した場合。大丈夫でしょう。

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

1
問題は、私が物事を比較するために使用したロジックにありました。私はこれに賛成します、うまくいけば、それがこのタイプの問題を探している誰かを助けることができるでしょう。
Lakatos Gyula 2013年

10
これは、コンパレータを修正するまで、物事を再び機能させるための迅速で醜い回避策としてのみ使用する必要があります。例外が発生した場合は、コンパレータにバグがあり、並べ替え順序がおかしいことを意味します。docs.oracle.com/javase/7/docs/api/java/util/…に記載されているすべてのルールを考慮する必要があります。
ハンス・ペーター・ストー

「奇妙な」が私が目指していたものである場合はどうなりますか?(a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}私はCollections.shuffle存在することを知っていますが、私は過度に保護されるのが嫌いです。
ジェフリー洞窟2016

古いアプリケーションがあり、JDK8でこのエラーが発生しました。JDK6を使用すると正しく動作するので、6にダウングレードするだけです。
StefanoScarpanti20年

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になり、がtrueになりますcardType > item.getCardType()。もう一度1を返します。

それから、o1 > o2o2 > o1。あなたは契約を破った。


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

ただし、card2.getRarity()がより小さい場合は-1をcard1.getRarity()返さない可能性があります。

同様に他のケースも見逃します。私はこれを行います、あなたはあなたの意図に応じて変えることができます:

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のこのバグレポートを見つけました:


1

同じ症状がありました。私の場合、ストリームで並べ替えが行われているときに、別のスレッドが比較対象のオブジェクトを変更していることがわかりました。この問題を解決するために、オブジェクトを不変の一時オブジェクトにマップし、ストリームを一時コレクションに収集して、その上で並べ替えを行いました。


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

単純な整数の2D配列であるn x 2 2D arraynamedをソートしようとしたときに、同様の問題が発生しましたcontests。これはほとんどの時間機能していましたが、1つの入力に対してランタイムエラーをスローしました:-

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;
        });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.