"¡El método de comparación viola su contrato general!"

188

¿Puede alguien explicarme en términos simples, por qué este código arroja una excepción, "El método de comparación viola su contrato general!", Y ¿cómo lo soluciono?

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}
n00bster
fuente
1
¿Cuál es el nombre y la clase de la excepción? ¿Es una excepción IlegalArgumentException? Si tuviera que adivinar, pensaría que deberías estar haciendo en s1.getParent().equals(s2)lugar de s1.getParent() == s2.
Freiheit
Y la excepción que también se lanza.
Matthew Farwell el
2
No sé mucho sobre Java o sobre las API de comparación de Java, pero este método de comparación parece estar completamente equivocado. Supongamos que s1es el padre de s2, y s2no es el padre de s1. Entonces compareParents(s1, s2)es 0, pero compareParents(s2, s1)es 1. Eso no tiene sentido. (Además, no es transitivo, como aix se menciona a continuación.)
mqp
44
Este error parece ser producido solo por una biblioteca específica cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/…
Peter Lawrey
en java, puede usar equals (devolver un booleano) o compareTo (devolver -1, 0 o +1). Anule estas funciones en su clase Foo y después de eso, puede verificar s1.getParent (). Equals (s2) ...
Mualig

Respuestas:

261

Tu comparador no es transitivo.

Dejar Aser el padre de B, y Bser el padre de C. Puesto que A > By B > C, entonces tiene que ser el caso de que A > C. Sin embargo, si se invoca su comparador Ay Cdevolverá cero, lo que significa A == C. Esto viola el contrato y por lo tanto arroja la excepción.

Es bastante amable por parte de la biblioteca detectar esto y hacerle saber, en lugar de comportarse de manera errática.

Una forma de satisfacer el requisito de transitividad compareParents()es atravesar la getParent()cadena en lugar de solo mirar al antepasado inmediato.

NPE
fuente
3
Introducido en java.util.Arrays.sort stackoverflow.com/questions/7849539/… de
leonbloy
46
El hecho de que la biblioteca esté detectando esto es asombroso. Alguien en el sol merece tirar un gigante De nada .
Qix - MONICA FUE MALTRATADA el
¿Puedes generalizar un poco esta respuesta para que esta pregunta sea más útil como publicación de referencia?
Bernhard Barker,
1
@Qix: tanto como amo a Sun, esto se agregó en Java 7 bajo el banner de Oracle
isapir el
1
@isapir Maldición! Buena atrapada.
Qix - MONICA FUE MALTRATADA el
38

Solo porque esto es lo que obtuve cuando busqué en Google este error, mi problema fue que tuve

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

el value >= other.valuedebe (obviamente) ser en realidad value > other.valuepor lo que en realidad se puede devolver 0 con objetos iguales.

you786
fuente
77
Tengo que agregar que si alguno de ustedes valuees un NaN (si valuees un doubleo float), también fallaría.
Matthieu
23

La violación del contrato a menudo significa que el comparador no proporciona el valor correcto o coherente al comparar objetos. Por ejemplo, es posible que desee realizar una comparación de cadenas y forzar cadenas vacías para ordenar hasta el final 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 );

Pero esto pasa por alto el caso en que AMBOS uno y dos están vacíos, y en ese caso, se devuelve el valor incorrecto (1 en lugar de 0 para mostrar una coincidencia), y el comparador informa que es una violación. Debería haber sido escrito como:

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 );
CESDewar
fuente
13

Incluso si su compareTo tiene la transitividad en teoría, a veces los errores sutiles arruinan las cosas ... como el error aritmético de coma flotante. Me pasó a mi. este fue mi código:

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 propiedad transitiva se mantiene claramente, pero por alguna razón estaba obteniendo la IllegalArgumentException. ¡Y resulta que debido a pequeños errores en la aritmética de coma flotante, los errores de redondeo causaron que la propiedad transitiva se rompiera donde no deberían! Así que reescribí el código para considerar diferencias realmente pequeñas 0, y funcionó:

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;
}   
Ali Mizan
fuente
2
Esto fue útil! Mi código estaba lógicamente bien, pero hubo un error debido al problema de precisión.
JSong
6

En nuestro caso, recibimos este error porque accidentalmente cambiamos el orden de comparación de s1 y s2. Así que ten cuidado con eso. Obviamente fue mucho más complicado que lo siguiente, pero esta es una ilustración:

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;
Tio Iroh
fuente
3

Java no verifica la consistencia en un sentido estricto, solo le notifica si se encuentra con problemas serios. Además, no le da mucha información del error.

Estaba desconcertado con lo que está sucediendo en mi clasificador e hice una consistencia estricta Chequeador, tal vez esto lo ayude:

/**
 * @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));
    }
  }
}
Martín
fuente
Simplemente invoque el método checkConsitency con la lista de parámetros y el comparador.
Martin
Tu código no se compila. Clases Compare, Convert(y potencialmente otros) no están definidos. Actualice el fragmento de código con un ejemplo autónomo.
Gili
Debe corregir el error tipográfico checkConsi(s)tencyy eliminar todas las @paramdeclaraciones redundantes para que el código sea más legible.
Roland Illig
3

En mi caso estaba haciendo algo como lo siguiente:

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

Lo que olvidé comprobar fue cuando tanto a.someField como b.someField son nulos.

jc12
fuente
3

He visto que esto sucede en un fragmento de código donde se realizó la comprobación a menudo recurrente de valores nulos:

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

Si compareParents(s1, s2) == -1entonces compareParents(s2, s1) == 1se espera. Con su código no siempre es cierto.

Específicamente si s1.getParent() == s2 && s2.getParent() == s1. Es solo uno de los posibles problemas.

Andrii Karaivanskyi
fuente
1

Editar la configuración de VM funcionó para mí.

-Djava.util.Arrays.useLegacyMergeSort=true
Rishav Roy
fuente
Verifique que mi intento de ayudarlo con el formato no haya roto nada. No estoy seguro sobre el -inicio de la solución propuesta. Tal vez quisiste algo así como una lista de viñetas de un elemento.
Yunnosch
2
También explique cómo esto ayuda con el problema descrito. Actualmente es prácticamente una respuesta de solo código.
Yunnosch
0

No puede comparar datos de objetos de esta manera: s1.getParent() == s2esto comparará las referencias de objetos. Debería anular la equals functionclase Foo y luego compararlos asís1.getParent().equals(s2)

erimerturk
fuente
No, en realidad creo que OP está tratando de ordenar una lista de algún tipo, y realmente quiere comparar referencias.
Edward Falk