"Il metodo di confronto viola il suo contratto generale!"


188

Qualcuno può spiegarmi in termini semplici, perché questo codice genera un'eccezione, "Il metodo di confronto viola il suo contratto generale!" E come posso risolverlo?

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

1
Qual è il nome e la classe dell'eccezione? È un'eccezione IllegalArgumentException? Se dovessi indovinare, penserei che dovresti fare s1.getParent().equals(s2)invece di s1.getParent() == s2.
Freiheit,

E anche l'eccezione che viene lanciata.
Matthew Farwell,

2
Non so molto su Java o sulle API di confronto Java, ma questo metodo di confronto sembra completamente sbagliato. Supponiamo che s1sia il genitore s2e s2non il genitore di s1. Quindi lo compareParents(s1, s2)è 0, ma lo compareParents(s2, s1)è 1. Non ha senso. (Inoltre, non è transitivo, come aix menzionato di seguito.)
mqp

4
Questo errore sembra essere prodotto solo da una libreria specifica cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/…
Peter Lawrey

in Java, puoi usare uguale a (restituisce un valore booleano) o compare To (restituisce -1, 0 o +1). Sostituisci queste funzioni nella tua classe Foo e dopo puoi controllare s1.getParent (). Equals (s2) ...
Mualig

Risposte:


261

Il tuo comparatore non è transitivo.

Lascia che Asia il genitore Be Bil genitore di C. Da A > Be B > C, allora deve essere il caso che A > C. Tuttavia, se il tuo comparatore è invocato su Ae C, restituirebbe zero, il che significa A == C. Ciò viola il contratto e quindi genera l'eccezione.

È piuttosto carino che la biblioteca lo rilevi e ti faccia sapere, piuttosto che comportarsi in modo irregolare.

Un modo per soddisfare il requisito di transitività compareParents()è attraversare la getParent()catena invece di guardare solo l'antenato immediato.


3
Introdotto nel Java 7 java.util.Arrays.sort stackoverflow.com/questions/7849539/...
leonbloy

46
Il fatto che la libreria lo rilevi è fantastico. Qualcuno al sole merita di buttare fuori un gigante . Prego .
Qix - MONICA È STATA MISTREATA il

Puoi generalizzare un po 'questa risposta per rendere questa domanda più utile come post di riferimento?
Bernhard Barker

1
@Qix - per quanto amo Sun, questo è stato aggiunto in Java 7 sotto il banner Oracle
isapir

1
@isapir Damn! Buona pesca.
Qix - MONICA È STATA MISTREATA l'

38

Solo perché questo è quello che ho ottenuto quando ho cercato su Google questo errore, il mio problema era che avevo

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

il value >= other.valuedovrebbe (ovviamente) effettivamente essere in value > other.valuemodo che tu possa effettivamente restituire 0 con oggetti uguali.


7
Devo aggiungere che se qualcuno dei tuoi valueè un NaN (se valueè un doubleo float), anche questo fallirebbe.
Matthieu,

23

La violazione del contratto spesso significa che il comparatore non fornisce il valore corretto o coerente durante il confronto di oggetti. Ad esempio, potresti voler eseguire un confronto tra stringhe e forzare le stringhe vuote per ordinare fino alla fine con:

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

Ma questo trascura il caso in cui SIA uno e due sono vuoti - e in quel caso, viene restituito il valore errato (1 invece di 0 per mostrare una corrispondenza), e il comparatore lo segnala come una violazione. Avrebbe dovuto essere scritto come:

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

Anche se il tuo confrontoTo trattiene la transitività in teoria, a volte i bug sottili rovinano le cose ... come l'errore aritmetico in virgola mobile. È successo a me. questo era il mio codice:

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;

}   

La proprietà transitiva è chiaramente valida, ma per qualche motivo stavo ottenendo IllegalArgumentException. E si scopre che a causa di piccoli errori nell'aritmetica in virgola mobile, gli errori di arrotondamento causano la rottura della proprietà transitiva dove non dovrebbero! Quindi ho riscritto il codice per considerare 0 differenze davvero minuscole, e ha funzionato:

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
Questo è stato utile! Il mio codice era logicamente ok ma c'era un errore a causa del problema di precisione.
JSong,

6

Nel nostro caso stavano ottenendo questo errore perché avevamo capovolto accidentalmente l'ordine di confronto di s1 e s2. Quindi stai attento. Era ovviamente molto più complicato del seguente ma questa è un'illustrazione:

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

3

Java non controlla la coerenza in senso stretto, ti avvisa solo se incontra seri problemi. Inoltre non ti dà molte informazioni dall'errore.

Sono rimasto perplesso su ciò che sta accadendo nel mio selezionatore e ho fatto una verifica costante della coerenza, forse questo ti aiuterà:

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

Richiama semplicemente il metodo checkConsitency con l'elenco dei parametri e il comparatore.
Martin,

Il tuo codice non viene compilato. Classi Compare, Convert(e potenzialmente altri) non sono definite. Aggiorna il codice sniplet con un esempio autonomo.
Gili,

È necessario correggere l'errore di battitura checkConsi(s)tencye rimuovere tutte le @paramdichiarazioni ridondanti per rendere il codice più leggibile.
Roland Illig,

3

Nel mio caso stavo facendo qualcosa di simile al seguente:

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

Quello che ho dimenticato di controllare era quando sia a.someField che b.someField sono nulli.


3

Ho visto questo accadere in un pezzo di codice in cui è stato eseguito il controllo spesso ricorrente per valori null:

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

2

Se compareParents(s1, s2) == -1allora compareParents(s2, s1) == 1è previsto. Con il tuo codice non è sempre vero.

In particolare se s1.getParent() == s2 && s2.getParent() == s1. È solo uno dei possibili problemi.


1

La modifica della configurazione della VM ha funzionato per me.

-Djava.util.Arrays.useLegacyMergeSort=true

Ricontrolla che il mio tentativo di aiutarti con la formattazione non ha interrotto nulla. Non sono sicuro -dell'inizio della soluzione proposta. Forse intendevi qualcosa di simile a un elenco di punti elenco di un elemento.
Yunnosch,

2
Spiega anche come questo aiuta con il problema descritto. Attualmente è praticamente solo una risposta di codice.
Yunnosch,

0

Non puoi confrontare i dati degli oggetti in questo modo: s1.getParent() == s2- questo confronterà i riferimenti agli oggetti. Dovresti eseguire l'override equals functionper la classe Foo e confrontarli in questo modos1.getParent().equals(s2)


No, in realtà penso che OP stia cercando di ordinare un elenco di qualche tipo e vuole confrontare effettivamente i riferimenti.
Edward Falk,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.