new - sort java
"¡El método de comparación viola su contrato general!" (9)
¿Puede alguien explicarme en términos simples, por qué este código lanza 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;
}
En mi caso, yo 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.
En nuestro caso, obtuvimos este error porque accidentalmente volteamos el orden de comparación de s1 y s2. Así que ten cuidado con eso. Obviamente fue mucho más complicado que el siguiente, pero esta es una ilustración:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
He visto esto suceder en un código en el que se realizaba la comprobación a menudo recurrente para valores nulos:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Incluso si su compareTo guarda la transitividad en teoría, a veces los errores sutiles ensucian las cosas ... como el error aritmético de coma flotante. Me pasó a mi. este era 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 claramente se cumple, pero por alguna razón obtuve la IllegalArgumentException. Y resulta que debido a pequeños errores en la aritmética de coma flotante, los errores de redondeo hacen que la propiedad transitiva se rompa donde no deberían. Así que reescribí el código para considerar las 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;
}
Java no comprueba la coherencia en un sentido estricto, solo le avisa si se encuentra con serios problemas. Además, no proporciona mucha información del error.
Estaba desconcertado con lo que sucedía en mi seleccionadora e hice una consistencia estricta. Controlador, quizás esto te 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));
}
}
}
La violación del contrato a menudo significa que el comparador no proporciona el valor correcto o consistente al comparar objetos. Por ejemplo, es posible que desee realizar una comparación de cadenas y forzar cadenas vacías para clasificarlas al 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 eso como una violación. Debería haberse 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 );
No puede comparar datos de objeto como este: s1.getParent() == s2
- esto comparará las referencias de objeto. Debería anular la equals function
para la clase Foo y luego compararlos como este s1.getParent().equals(s2)
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.value
debería (obviamente) ser value > other.value
para que puedas devolver 0 con objetos iguales.
Su comparador no es transitivo.
Deje A
ser el padre de B
, y B
sea el padre de C
Como A > B
y B > C
, entonces debe ser el caso que A > C
Sin embargo, si su comparador se invoca en A
y C
, devolvería cero, lo que significa A == C
Esto viola el contrato y por lo tanto arroja la excepción.
Es bastante agradable de la biblioteca detectar esto y hacerte saber, en lugar de comportarse erráticamente.
Una forma de satisfacer el requisito de transitividad en compareParents()
es atravesar la cadena getParent()
lugar de solo mirar al antecesor inmediato.