it-swarm.com.de

Java-Fehler: Vergleichsmethode verstößt gegen den allgemeinen Vertrag

Ich habe viele Fragen dazu gesehen und versucht, das Problem zu lösen, aber nach einer Stunde Googeln und viel Trial & Error kann ich es immer noch nicht beheben. Ich hoffe, einige von Ihnen werden das Problem erkennen.

Das bekomme ich:

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

Und das ist mein Vergleicher:

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

Irgendeine Idee?

55
Lakatos Gyula

Die Ausnahmemeldung ist eigentlich ziemlich beschreibend. Der Vertrag, den er erwähnt, ist Transitivität : Wenn A > B und B > C, dann für jede A, B und C: A > C. Ich habe es mit Papier und Bleistift überprüft und Ihr Code scheint ein paar Löcher zu haben:

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

sie geben -1 nicht zurück, wenn card1.getRarity() > card2.getRarity().


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

Sie geben -1 zurück, wenn die IDs nicht gleich sind. Sie sollten -1 oder 1 zurückgeben, je nachdem, welche ID größer war.


Schau dir das an. Abgesehen von der besseren Lesbarkeit sollte es eigentlich funktionieren:

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

Es hat auch etwas mit der Version von JDK zu tun. Wenn es in JDK6 gut läuft, hat es vielleicht das Problem in JDK 7, das Sie beschrieben haben, weil die Implementierungsmethode in Jdk 7 geändert wurde.

Schau dir das an:

Beschreibung: Der von Java.util.Arrays.sort und (indirekt) von Java.util.Collections.sort verwendete Sortieralgorithmus wurde ersetzt. Die neue Sortierimplementierung kann eine IllegalArgumentException auslösen, wenn eine Comparable erkannt wird, die den Comparable-Vertrag verletzt. In der vorherigen Implementierung wurde eine solche Situation ignoriert. Wenn das vorherige Verhalten gewünscht wird, können Sie die neue Systemeigenschaft Java.util.Arrays.useLegacyMergeSort verwenden, um das vorherige Mergesort-Verhalten wiederherzustellen.

Ich kenne den genauen Grund nicht. Wenn Sie jedoch den Code hinzufügen, bevor Sie die Sortierung verwenden. Es wird in Ordnung sein.

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

Sie können die folgende Klasse verwenden, um Transitivitätsfehler in Ihren Comparators zu ermitteln:

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

Rufen Sie einfach Comparators.verifyTransitivity(myComparator, myCollection) vor dem fehlgeschlagenen Code auf.

26
Gili

Betrachten Sie den folgenden Fall:

Zuerst wird o1.compareTo(o2) aufgerufen. card1.getSet() == card2.getSet() ist zufällig wahr und card1.getRarity() < card2.getRarity() auch, also geben Sie 1 zurück.

Dann wird o2.compareTo(o1) aufgerufen. Wieder ist card1.getSet() == card2.getSet() wahr. Dann springen Sie zur folgenden else, dann ist card1.getId() == card2.getId() wahr und cardType > item.getCardType(). Sie kehren wieder 1 zurück.

Von diesem o1 > o2 und o2 > o1. Sie haben den Vertrag gebrochen.

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

Wenn jedoch card2.getRarity() kleiner als card1.getRarity() ist, können Sie -1 nicht zurückgeben.

Sie vermissen auch andere Fälle. Ich würde das tun, Sie können sich je nach Absicht ändern:

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

Ich musste nach verschiedenen Kriterien sortieren (Datum und, wenn dasselbe Datum; andere Dinge ...). Was bei Eclipse mit einer älteren Java-Version funktionierte, funktionierte bei Android nicht mehr: Vergleichsmethode verletzt Vertrag ...

Nach dem Lesen von StackOverflow habe ich eine separate Funktion geschrieben, die ich von compare () aufgerufen habe, wenn die Datumsangaben gleich sind. Diese Funktion berechnet die Priorität gemäß den Kriterien und gibt -1, 0 oder 1 an compare () zurück. Es scheint jetzt zu funktionieren. 

0
Jean Burkhardt

Ich habe den gleichen Fehler mit einer Klasse wie der folgenden StockPickBean erhalten. Aus diesem Code aufgerufen:

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

Nachdem Sie den gleichen Fehler erhalten haben: 

Java.lang.IllegalArgumentException: Vergleichsmethode verletzt allgemeinen Vertrag!

Ich habe diese Zeile geändert:

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

zu:

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

Das behebt den Fehler.

0
pmkent