lists - java compare list of objects
Error de Java: el método de comparación viola su contrato general (7)
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() < card2.getRarity()) {
return 1;
} else {
if (card1.getId() == card2.getId()) {
if (cardType > item.getCardType()) {
return 1;
} else {
if (cardType == item.getCardType()) {
return 0;
}
return -1;
}
}
return -1;
}
}
return 1;
}
}
¿Alguna idea?
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.
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!
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 <T> 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 <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> 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.
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");
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 , 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<StockPickBean> beansListcatMap.getValue();
beansList.sort(StockPickBean.Comparators.VALUE);
public class StockPickBean implements Comparable<StockPickBean> {
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<StockPickBean> 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<StockPickBean> VALUE = (val1, val2) -> (int)
(val1.value - val2.value);
a:
public static Comparator<StockPickBean> VALUE = (StockPickBean spb1,
StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);
Eso soluciona el error.
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;
}
}