「比較方法は一般契約に違反しています!」


187

誰かが簡単な言葉で私を説明できますか?なぜこのコードが例外をスローするのですか?

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}

1
例外の名前とクラスは何ですか?IllegalArgumentExceptionですか?私が推測しなければならない場合、私はあなたのs1.getParent().equals(s2)代わりにやるべきだと思いますs1.getParent() == s2
フライハイト

また、スローされる例外。
マシュー・ファーウェル

2
JavaやJava比較APIについてはあまり知りませんが、この比較方法はまったく間違っているようです。仮定s1の親であるs2、とs2の親ではありませんs1。その後compareParents(s1, s2)0、しかしcompareParents(s2, s1)です1。それは意味がありません。(さらに、下記のaixのように推移的ではありません。)
mqp


Javaでは、equals(ブール値を返す)またはcompareTo(-1、0、または+1を返す)を使用できます。Fooクラスでこの関数をオーバーライドします。その後、s1.getParent()。equals(s2)...
Mualig

回答:


261

コンパレータは推移的ではありません。

みましょうAの親になるB、とBの親になりますC。以来A > BB > C、それはそのような場合でなければなりませんA > C。ただし、コンパレータがAand Cで呼び出された場合、ゼロを返しますA == C。これは契約に違反しているため、例外がスローされます。

これを検出して通知することは、不規則な動作をするのではなく、ライブラリのほうがいいです。

の推移性要件を満たす1つの方法は、直接の祖先だけを見るのではなくcompareParents()getParent()チェーンをトラバースすることです。


3
Java 7のjava.util.Arrays.sort stackoverflow.com/questions/7849539/…で
leonbloy

46
ライブラリがこれを検出しているという事実は素晴らしいです。太陽の誰かが巨大捨てるに値するどういたしまして
Qix-モニカは2017年

この質問を参照投稿としてより有用にするために、この回答を少し一般化できますか?
Bernhard Barker

1
@Qix-私がSunを愛しているのと同じくらい、これはJava 7のOracleバナーの下に追加されました
isapir

1
@isapirくそー!良いキャッチ。
Qix-モニカは

38

これが私がこのエラーをグーグル検索したときに得たものだからといって、私の問題は

if (value < other.value)
  return -1;
else if (value >= other.value)
  return 1;
else
  return 0;

これvalue >= other.valueは(当然のことながら)実際にvalue > other.valueは等しいオブジェクトで0を返すことができるようにするためです。


7
私はあなたのいずれかがあればことを追加する必要がありますvalue(場合はNaNをされvalueているdoublefloat)、それは同様に失敗します。
Matthieu 2014

22

コントラクトの違反は、オブジェクトを比較するときにコンパレーターが正しい値または一貫した値を提供していないことを意味します。たとえば、文字列の比較を実行し、空の文字列を強制的に最後にソートするには、次のようにします。

if ( one.length() == 0 ) {
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

しかし、これは1と2の両方が空であるケースを見落とします-その場合、誤った値が返され(一致を示すために0ではなく1)、コンパレータは違反として報告します。それは次のように書かれるべきでした:

if ( one.length() == 0 ) {
    if ( two.length() == 0 ) {
        return 0;               // BOth empty - so indicate
    }
    return 1;                   // empty string sorts last
}
if ( two.length() == 0 ) {
    return -1;                  // empty string sorts last                  
}
return one.compareToIgnoreCase( two );

13

compareToが理論的に推移性を保持している場合でも、浮動小数点演算エラーなどの微妙なバグが混乱することがあります。それは私に起こりました。これは私のコードでした:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    else
        return 0;

}   

推移的なプロパティは明らかに保持されますが、何らかの理由でIllegalArgumentExceptionが発生しました。そして、浮動小数点演算の小さなエラーのために、推移的プロパティが本来の場所で壊れないようにする丸めエラーが発生します。だから私は本当に小さな違い0を考慮するようにコードを書き直しました、そしてそれはうまくいきました:

public int compareTo(tfidfContainer compareTfidf) {
    //descending order
    if ((this.tfidf - compareTfidf.tfidf) < .000000001)
        return 0;
    if (this.tfidf > compareTfidf.tfidf)
        return -1;
    else if (this.tfidf < compareTfidf.tfidf)
        return 1;
    return 0;
}   

2
これは役に立ちました!私のコードは論理的には大丈夫ですが、精度の問題のためにエラーが発生しました。
JSong 2018

6

私たちの場合、誤ってs1とs2の比較順序を逆にしたため、このエラーが発生していました。だから気をつけて。これは明らかに次の方法よりもはるかに複雑でしたが、これは図解です。

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;

3

Javaは厳密な意味で整合性をチェックせず、重大な問題が発生した場合にのみ通知します。また、エラーから多くの情報を提供しません。

私のソーターで何が起こっているのか困惑し、厳密な整合性チェッカーを作成しました。

/**
 * @param dailyReports
 * @param comparator
 */
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
  final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();

  iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
    /**
     * @param o1
     * @param o2
     */
    @Override
    public void pair(T o1, T o2) {
      final int diff = comparator.compare(o1, o2);
      if (diff < Compare.EQUAL) {
        checkConsistency(objectMapSmallerOnes, o1, o2);
        getListSafely(objectMapSmallerOnes, o2).add(o1);
      } else if (Compare.EQUAL < diff) {
        checkConsistency(objectMapSmallerOnes, o2, o1);
        getListSafely(objectMapSmallerOnes, o1).add(o2);
      } else {
        throw new IllegalStateException("Equals not expected?");
      }
    }
  });
}

/**
 * @param objectMapSmallerOnes
 * @param o1
 * @param o2
 */
static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
  final List<T> smallerThan = objectMapSmallerOnes.get(o1);

  if (smallerThan != null) {
    for (final T o : smallerThan) {
      if (o == o2) {
        throw new IllegalStateException(o2 + "  cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
      }
      checkConsistency(objectMapSmallerOnes, o, o2);
    }
  }
}

/**
 * @param keyMapValues 
 * @param key 
 * @param <Key> 
 * @param <Value> 
 * @return List<Value>
 */ 
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
  List<Value> values = keyMapValues.get(key);

  if (values == null) {
    keyMapValues.put(key, values = new LinkedList<Value>());
  }

  return values;
}

/**
 * @author Oku
 *
 * @param <T>
 */
public interface IPairIteratorCallback<T> {
  /**
   * @param o1
   * @param o2
   */
  void pair(T o1, T o2);
}

/**
 * 
 * Iterates through each distinct unordered pair formed by the elements of a given iterator
 *
 * @param it
 * @param callback
 */
public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
  List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {

    @Override
    public Iterator<T> iterator() {
      return it;
    }

  });

  for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
    for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
      callback.pair(list.get(outerIndex), list.get(innerIndex));
    }
  }
}

パラメータリストとコンパレータを使用して、checkConsitencyメソッドを呼び出すだけです。
マーティン

コードがコンパイルされません。クラスCompareConvert(および潜在的に他のもの)が定義されていません。自己完結型の例でコードスニペットを更新してください。
Gili

コードを読みやすくするには、タイプミスを修正し、checkConsi(s)tencyすべての冗長な@param宣言を削除する必要があります。
Roland Illig 2017

3

私の場合、私は次のようなことをしていました:

if (a.someField == null) {
    return 1;
}

if (b.someField == null) {
    return -1;
}

if (a.someField.equals(b.someField)) {
    return a.someOtherField.compareTo(b.someOtherField);
}

return a.someField.compareTo(b.someField);

チェックするのを忘れていたのは、a.someFieldとb.someFieldの両方がnullの場合です。


3

これは、null値の定期的なチェックが頻繁に実行されるコードで発生することを確認しました。

if(( A==null ) && ( B==null )
  return +1;//WRONG: two null values should return 0!!!

2

compareParents(s1, s2) == -1それcompareParents(s2, s1) == 1が予想される場合。あなたのコードでは、それは常に正しいとは限りません。

具体的にはs1.getParent() == s2 && s2.getParent() == s1。これは考えられる問題の1つにすぎません。


1

VM構成の編集がうまくいきました。

-Djava.util.Arrays.useLegacyMergeSort=true

書式設定を支援する私の試みが何も壊していないことを再確認してください。私は-提案された解決策の始まりについて確信が持てません。代わりに、1項目の箇条書きリストのようなものを意図していたのかもしれません。
ユンノシュ

2
また、これが上記の問題にどのように役立つかを説明してください。現在、これは実際にはコードのみの回答です。
ユンノシュ

0

次のようにオブジェクトデータを比較することはできません:s1.getParent() == s2-これはオブジェクト参照を比較します。あなたはequals functionFooクラスをオーバーライドしてから、このように比較する必要がありますs1.getParent().equals(s2)


いいえ、実際には、OPはある種のリストを並べ替えようとしており、実際に参照を比較したいと思います。
Edward Falk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.