web-dev-qa-db-fra.com

Erreur Java: la méthode de comparaison viole son contrat général

J'ai vu beaucoup de questions à ce sujet et j'ai essayé de résoudre le problème, mais après une heure de recherches sur Google et de nombreux essais et erreurs, je ne peux toujours pas le résoudre. J'espère que certains d'entre vous attrapent le problème.

Voici ce que je reçois:

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

Et voici mon comparateur:

@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;
    }
}

Une idée?

55
Lakatos Gyula

Le message d'exception est en fait assez descriptif. Le contrat mentionné est transitivity : if A > B et B > C, puis pour toutes les variables A, B et C: A > C. Je l'ai vérifié avec du papier et un crayon et votre code semble avoir quelques trous:

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

vous ne renvoyez pas -1 si card1.getRarity() > card2.getRarity().


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

Vous renvoyez -1 si les identifiants ne sont pas égaux. Vous devriez renvoyer -1 ou 1 en fonction de l'identifiant le plus grand.


Regarde ça. En plus d'être beaucoup plus lisible, je pense que ça devrait marcher:

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!
70
Tomasz Nurkiewicz

Cela a aussi quelque chose à voir avec la version de JDK . Si cela fonctionne bien dans JDK6, le problème décrit dans JDK 7 sera peut-être décrit par vous, car la méthode de mise en œuvre de jdk 7 a été modifiée.

Regarde ça:

Description: l’algorithme de tri utilisé par Java.util.Arrays.sort et (indirectement) par Java.util.Collections.sort a été remplacé. La nouvelle implémentation de tri peut générer une IllegalArgumentException si elle détecte une Comparable qui enfreint le contrat Comparable. L'implémentation précédente ignorait silencieusement une telle situation. Si le comportement précédent est souhaité, vous pouvez utiliser la nouvelle propriété système, Java.util.Arrays.useLegacyMergeSort, pour restaurer le comportement de mergesort précédent.

Je ne connais pas la raison exacte. Toutefois, si vous ajoutez le code avant d’utiliser le tri. Ça va aller.

System.setProperty("Java.util.Arrays.useLegacyMergeSort", "true");
27
Justin Civi

Vous pouvez utiliser la classe suivante pour identifier les bogues de transitivité dans vos comparateurs:

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

Appelez simplement Comparators.verifyTransitivity(myComparator, myCollection) devant le code qui a échoué.

26
Gili

Considérons le cas suivant:

Tout d'abord, o1.compareTo(o2) est appelé. card1.getSet() == card2.getSet() se trouve être vrai et il en est de même card1.getRarity() < card2.getRarity(). Vous renvoyez donc 1.

Ensuite, o2.compareTo(o1) est appelé. Encore une fois, card1.getSet() == card2.getSet() est vrai. Ensuite, vous passez à la else suivante, puis card1.getId() == card2.getId() est vrai et cardType > item.getCardType(). Vous revenez 1 à nouveau.

À partir de cela, o1 > o2 et o2 > o1. Vous avez rompu le contrat.

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

Toutefois, si card2.getRarity() est inférieur à card1.getRarity(), vous risquez de ne pas renvoyer -1 .

De même, vous manquez d'autres cas. Je ferais cela, vous pouvez changer en fonction de votre intention:

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;

    }
}
2
Joe

J'ai dû trier sur plusieurs critères (date, et, si même date; autres choses ...). Ce qui fonctionnait sur Eclipse avec une version plus ancienne de Java, ne fonctionnait plus sous Android: la méthode de comparaison viole le contrat ...

Après avoir lu sur StackOverflow, j’ai écrit une fonction distincte que j’ai appelée depuis compare () si les dates sont les mêmes. Cette fonction calcule la priorité, en fonction des critères, et renvoie -1, 0 ou 1 pour comparer (). Ça semble fonctionner maintenant. 

0
Jean Burkhardt

J'ai eu la même erreur avec une classe comme celle-ci StockPickBean. Appelé à partir de ce code:

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

Après avoir obtenu la même erreur: 

Java.lang.IllegalArgumentException: la méthode de comparaison enfreint son contrat général!

J'ai changé cette ligne:

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

à:

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

Cela corrige l'erreur.

0
pmkent