“Karşılaştırma yöntemi genel sözleşmesini ihlal ediyor!”


187

Birisi bana basit terimlerle açıklayabilir mi, bu kod neden bir istisna oluşturur, "Karşılaştırma yöntemi genel sözleşmesini ihlal eder!" Ve nasıl düzeltebilirim?

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

1
İstisnanın adı ve sınıfı nedir? Bir IllegalArgumentException mı? Tahmin etmek zorunda kalsaydım bunun s1.getParent().equals(s2)yerine yapman gerektiğini düşünürdüm s1.getParent() == s2.
Freiheit

Ve atılan istisna da.
Matthew Farwell

2
Java veya Java karşılaştırma API'ları hakkında fazla bir şey bilmiyorum, ancak bu karşılaştırma yöntemi ölü gibi görünüyor. Varsayalım s1üstüdür s2ve s2üst öğesi değil s1. Sonra compareParents(s1, s2)öyle 0, ama compareParents(s2, s1)öyle 1. Bu mantıklı değil. (Ayrıca, aşağıda belirtilen aix gibi geçişli değildir.)
mqp

4
Bu hata yalnızca belirli bir kütüphane tarafından üretildiği görülmektedir cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/...
Peter Lawrey

java'da eşittir (bir boole döndür) veya CompareTo (-1, 0 veya +1 döndür) kullanabilirsiniz. Foo sınıfınızdaki bu işlevleri geçersiz kılın ve bundan sonra s1.getParent (). Eşittir (s2) ...
Mualig

Yanıtlar:


261

Karşılaştırıcınız geçişli değil.

Izin vermek Aebeveyni Bve Bebeveyni C. Yana A > Bve B > Co zaman bu durumda olmalıdır A > C. Ancak, karşılaştırıcınız çağrılırsa Ave Csıfır döndürür, yani A == C. Bu, sözleşmeyi ihlal eder ve bu nedenle istisnayı atar.

Düzensiz davranmak yerine, bunu saptamak ve size bildirmek oldukça kütüphanedir.

İçinde geçişlilik gereksinimi karşılamak için bir yolu compareParents()çapraz etmektir getParent()yalnızca acil atası görmenin ötesinde zinciri.


3
Java 7'nin java.util.Arrays.sort stackoverflow.com/questions/7849539/…
leonbloy

46
Kütüphane bunu tespit ediyor olması harika. Güneşe Birisi dev dışarı atmak için hak Rica ederim .
Qix - MONICA

Bu soruyu referans yazısı olarak daha kullanışlı hale getirmek için bu cevabı biraz genelleştirebilir misiniz?
Bernhard Barker

1
@Qix - Sun'ı sevdiğim kadar, bu Java 7'de Oracle bayrağı altında eklendi
isapir

1
@isapir Lanet olsun! İyi yakalama.
Qix - MONICA

38

Bu hatayı Google'a aldığımda aldığım şey bu olduğundan, sorunum şuydu:

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

value >= other.value(besbelli) aslında olmalı value > other.valueaslında eşit nesnelerle 0 geri dönebilmek için.


7
Herhangi valuebir NaN (eğer valuebir doubleya da float) ise, o da başarısız olacağını eklemek zorundayım .
Matthieu

22

Sözleşmenin ihlali çoğu zaman karşılaştırıcının nesneleri karşılaştırırken doğru veya tutarlı bir değer sağlamadığı anlamına gelir. Örneğin, bir dize karşılaştırma gerçekleştirmek ve boş dizeleri sonuna kadar sıralamaya zorlamak isteyebilirsiniz:

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

Ancak bu, İKİ bir ve ikisinin boş olduğu duruma bakar - ve bu durumda yanlış değer döndürülür (eşleşme göstermek için 0 yerine 1 yerine) ve karşılaştırıcı bunu bir ihlal olarak bildirir. Aşağıdaki gibi yazılmalıdır:

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 teoride geçiş tutarlığı olsa bile, bazen ince hatalar şeyleri karıştırır ... kayan nokta aritmetik hatası gibi. Bana oldu. bu benim kodum oldu:

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;

}   

Geçişli özellik açıkça tutar, ancak bir nedenle IllegalArgumentException alıyordum. Ve kayan nokta aritmetiğindeki küçük hatalar nedeniyle, geçişli özelliğin, olmaması gereken yerde kırılmasına neden olan yuvarlama hatalarının ortaya çıktığı ortaya çıkıyor! Bu yüzden kodu gerçekten küçük farkları 0 dikkate almak için yeniden yazdım ve işe yaradı:

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
Bu yardımcı oldu! Kodum mantıksal olarak iyiydi ama hassas sorun nedeniyle bir hata oluştu.
JSong

6

Bizim durumumuzda bu hatayı alıyorduk çünkü yanlışlıkla s1 ve s2 karşılaştırma sırasını tersine çevirmiştik. Öyleyse dikkat et. Açıkçası aşağıdakilerden çok daha karmaşıktı ama bu bir örnek:

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

3

Java tutarlılığı sıkı bir şekilde kontrol etmez, sadece ciddi bir sorunla karşılaşırsa sizi uyarır. Ayrıca hatadan çok fazla bilgi vermez.

Sıralayıcımda olanlarla şaşkına döndüm ve katı bir tutarlılık yaptımChecker, belki bu size yardımcı olacaktır:

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

Parametre listesi ve karşılaştırıcı ile checkConsitency metodunu çağırmanız yeterlidir.
Martin

Kodunuz derlenmiyor. Sınıflar Compare, Convert(ve diğerleri) tarif edilmez. Lütfen kod sniplet'ini bağımsız bir örnekle güncelleyin.
Gili

Kodu daha okunabilir hale getirmek için yazım hatasını düzeltmeli checkConsi(s)tencyve tüm gereksiz @parambildirimleri kaldırmalısınız .
Roland Illig

3

Benim durumumda aşağıdaki gibi bir şey yapıyordum:

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

Kontrol etmeyi unuttuğum şey, hem a.someField hem de b.someField boş olduğunda.


3

Bu null değerleri için sık sık yinelenen kontrol yapıldığı bir kod parçası olduğunu gördüm:

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

2

Eğer compareParents(s1, s2) == -1öyleyse compareParents(s2, s1) == 1bekleniyor. Kodunuzla bu her zaman doğru değildir.

Özellikle eğer s1.getParent() == s2 && s2.getParent() == s1. Olası sorunlardan sadece biri.


1

VM Yapılandırmasını düzenlemek benim için çalıştı.

-Djava.util.Arrays.useLegacyMergeSort=true

Lütfen biçimlendirmede size yardımcı olma girişimimin hiçbir şeyi bozmadığını tekrar kontrol edin. -Önerilen çözümün başlangıcı hakkında emin değilim . Belki de bunun yerine tek maddelik bir madde işareti listesi gibi bir şey istediniz.
Yunnosch

2
Ayrıca, bunun açıklanan soruna nasıl yardımcı olduğunu da açıklayın. Şu anda pratik olarak sadece kodlu bir cevap.
Yunnosch

0

Bunun gibi nesne verilerini karşılaştıramazsınız: s1.getParent() == s2- bu nesne referanslarını karşılaştırır. equals functionFoo sınıfı için geçersiz kılmalı ve bunları şu şekilde karşılaştırmalısınızs1.getParent().equals(s2)


Hayır, aslında OP'nin bir çeşit listeyi sıralamaya çalıştığını ve aslında referansları karşılaştırmak istediğini düşünüyorum.
Edward Falk
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.