Error de Java: el método de comparación viola su contrato general

Vi muchas preguntas al respecto y traté de resolver el problema, pero después de una hora de búsqueda en Google y un montón de prueba y error, todavía no puedo solucionarlo. Espero que algunos de ustedes capten el problema.

Esto es lo que obtengo:

java.lang.IllegalArgumentException: Comparison method violates its general contract! at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835) at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453) at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392) at java.util.ComparableTimSort.sort(ComparableTimSort.java:191) at java.util.ComparableTimSort.sort(ComparableTimSort.java:146) at java.util.Arrays.sort(Arrays.java:472) at java.util.Collections.sort(Collections.java:155) ... 

Y este es mi comparador:

 @Override public int compareTo(Object o) { if(this == o){ return 0; } CollectionItem item = (CollectionItem) o; Card card1 = CardCache.getInstance().getCard(cardId); Card card2 = CardCache.getInstance().getCard(item.getCardId()); if (card1.getSet() < card2.getSet()) { return -1; } else { if (card1.getSet() == card2.getSet()) { if (card1.getRarity()  item.getCardType()) { return 1; } else { if (cardType == item.getCardType()) { return 0; } return -1; } } return -1; } } return 1; } } 

¿Alguna idea?

El mensaje de excepción es bastante descriptivo. El contrato que menciona es transitividad : si A > B y B > C entonces para cualquier A , B y C : A > C Lo revisé con papel y lápiz y tu código parece tener algunos agujeros:

 if (card1.getRarity() < card2.getRarity()) { return 1; 

no devuelve -1 si card1.getRarity() > card2.getRarity() .


 if (card1.getId() == card2.getId()) { //... } return -1; 

Usted devuelve -1 si los ids no son iguales. Deberías devolver -1 o 1 dependiendo de qué identificación era más grande.


Mira esto. Además de ser mucho más legible, creo que debería funcionar:

 if (card1.getSet() > card2.getSet()) { return 1; } if (card1.getSet() < card2.getSet()) { return -1; }; if (card1.getRarity() < card2.getRarity()) { return 1; } if (card1.getRarity() > card2.getRarity()) { return -1; } if (card1.getId() > card2.getId()) { return 1; } if (card1.getId() < card2.getId()) { return -1; } return cardType - item.getCardType(); //watch out for overflow! 

También tiene algo que ver con la versión de JDK. Si funciona bien en JDK6, tal vez tenga el problema en JDK 7 descrito por usted, porque el método de implementación en jdk 7 ha cambiado.

Mira este:

Descripción: el algoritmo de clasificación utilizado por java.util.Arrays.sort y (indirectamente) por java.util.Collections.sort se ha reemplazado. La nueva implementación de clasificación puede arrojar una IllegalArgumentException si detecta un Comparable que viola el contrato Comparable . La implementación anterior ignoró silenciosamente tal situación. Si se desea el comportamiento anterior, puede usar la nueva propiedad del sistema, java.util.Arrays.useLegacyMergeSort , para restablecer el comportamiento de mergesort anterior.

No sé el motivo exacto. Sin embargo, si agrega el código antes de usar ordenar. Estará bien.

 System.setProperty("java.util.Arrays.useLegacyMergeSort", "true"); 

Puede usar la siguiente clase para identificar errores de transitividad en sus Comparadores:

 /** * @author Gili Tzabari */ public final class Comparators { /** * Verify that a comparator is transitive. * * @param  the type being compared * @param comparator the comparator to test * @param elements the elements to test against * @throws AssertionError if the comparator is not transitive */ public static  void verifyTransitivity(Comparator comparator, Collection elements) { for (T first: elements) { for (T second: elements) { int result1 = comparator.compare(first, second); int result2 = comparator.compare(second, first); if (result1 != -result2) { // Uncomment the following line to step through the failed case //comparator.compare(first, second); throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 + " but swapping the parameters returns " + result2); } } } for (T first: elements) { for (T second: elements) { int firstGreaterThanSecond = comparator.compare(first, second); if (firstGreaterThanSecond < = 0) continue; for (T third: elements) { int secondGreaterThanThird = comparator.compare(second, third); if (secondGreaterThanThird <= 0) continue; int firstGreaterThanThird = comparator.compare(first, third); if (firstGreaterThanThird <= 0) { // Uncomment the following line to step through the failed case //comparator.compare(first, third); throw new AssertionError("compare(" + first + ", " + second + ") > 0, " + "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " + firstGreaterThanThird); } } } } } /** * Prevent construction. */ private Comparators() { } } 

Simplemente invoque Comparators.verifyTransitivity(myComparator, myCollection) delante del código que falla.

Considere el siguiente caso:

Primero, se o1.compareTo(o2) . card1.getSet() == card2.getSet() pasa a ser verdadero y también lo es card1.getRarity() < card2.getRarity() , por lo que devuelve 1.

Entonces, se o2.compareTo(o1) . De nuevo, card1.getSet() == card2.getSet() es verdadero. Luego, salte a lo siguiente, luego card1.getId() == card2.getId() pasa a ser verdadero, y también lo es cardType > item.getCardType() . Usted regresa 1 de nuevo.

De eso, o1 > o2 y o2 > o1 . Rompiste el contrato.

  if (card1.getRarity() < card2.getRarity()) { return 1; 

Sin embargo, si card2.getRarity() es menor que card1.getRarity() es posible que no devuelva -1 .

De manera similar, te pierdes otros casos. Haría esto, puedes cambiar dependiendo de tu intención:

 public int compareTo(Object o) { if(this == o){ return 0; } CollectionItem item = (CollectionItem) o; Card card1 = CardCache.getInstance().getCard(cardId); Card card2 = CardCache.getInstance().getCard(item.getCardId()); int comp=card1.getSet() - card2.getSet(); if (comp!=0){ return comp; } comp=card1.getRarity() - card2.getRarity(); if (comp!=0){ return comp; } comp=card1.getSet() - card2.getSet(); if (comp!=0){ return comp; } comp=card1.getId() - card2.getId(); if (comp!=0){ return comp; } comp=card1.getCardType() - card2.getCardType(); return comp; } } 

Tuve que ordenar varios criterios (fecha y, si es la misma fecha, otras cosas …). Lo que funcionaba en Eclipse con una versión anterior de Java ya no funcionaba en Android: el método de comparación viola el contrato …

Después de leer en StackOverflow, escribí una función separada a la que llamé desde compare () si las fechas son las mismas. Esta función calcula la prioridad, según los criterios, y devuelve -1, 0 o 1 para comparar (). Parece que funciona ahora.

StockPickBean el mismo error con una clase como la siguiente StockPickBean . Llamado desde este código:

 List beansListcatMap.getValue(); beansList.sort(StockPickBean.Comparators.VALUE); public class StockPickBean implements Comparable { private double value; public double getValue() { return value; } public void setValue(double value) { this.value = value; } @Override public int compareTo(StockPickBean view) { return Comparators.VALUE.compare(this,view); //return Comparators.SYMBOL.compare(this,view); } public static class Comparators { public static Comparator VALUE = (val1, val2) -> (int) (val1.value - val2.value); } } 

Después de obtener el mismo error:

java.lang.IllegalArgumentException: ¡El método de comparación viola su contrato general!

Cambié esta línea:

 public static Comparator VALUE = (val1, val2) -> (int) (val1.value - val2.value); 

a:

 public static Comparator VALUE = (StockPickBean spb1, StockPickBean spb2) -> Double.compare(spb2.value,spb1.value); 

Eso soluciona el error.