Błąd Java: metoda porównywania narusza ogólną umowę

Widziałem wiele pytań na ten temat i próbowałem rozwiązać problem, ale po godzinie googlowania i wielu próbach i błędach nadal nie mogę go naprawić. Mam nadzieję, że niektórzy z was złapią problem.

Oto co dostaję:

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

A to jest mój komparator:

@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;
    }
}
Jakiś pomysł?
Author: Lakatos Gyula, 2012-07-12

7 answers

Wiadomość o wyjątku jest właściwie dość opisowa. Kontrakt, o którym mówi, to przechodniość: Jeśli A > B i B > C to dla dowolnego A, B oraz C: A > C. Sprawdziłem to papierem i ołówkiem i twój kod wydaje się mieć kilka dziur:

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

Nie zwracasz -1 Jeśli card1.getRarity() > card2.getRarity().


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

Zwracasz -1 jeśli identyfikatory nie są równe. Należy zwrócić -1 lub 1 w zależności od tego, który identyfikator był większy.


Spójrz na to. Poza byciem o wiele bardziej czytelny, myślę, że faktycznie powinien działać:
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!
 68
Author: Tomasz Nurkiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 21:49:33

Ma też coś wspólnego z wersją JDK. Jeśli dobrze się sprawdzi w JDK6, może będzie miał opisany przez Ciebie problem w JDK 7, ponieważ metoda implementacji w jdk 7 została zmieniona.

Zobacz też:]}

Opis: algorytm sortowania używany przez java.util.Arrays.sort i (pośrednio) przez java.util.Collections.sort został zastąpiony. Nowa implementacja sortowania może rzucić IllegalArgumentException, Jeśli wykryje Comparable, które naruszają kontrakt Comparable. Poprzednia implementacja po cichu ignorowała takie sytuacja. Jeśli poprzednie zachowanie jest pożądane, można użyć nowej właściwości systemowej, java.util.Arrays.useLegacyMergeSort, aby przywrócić poprzednie zachowanie mergesort.

Nie znam dokładnego powodu. Jeśli jednak dodasz kod przed użyciem sortowania. Będzie dobrze.
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
 27
Author: Justin Civi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-18 13:58:54

Możesz użyć poniższej klasy, aby wskazać błędy przechodniości w swoich Komparatorach:

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

Po prostu wywołaj Comparators.verifyTransitivity(myComparator, myCollection) przed kodem, który się nie powiedzie.

 24
Author: Gili,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-15 17:16:21

Rozważ następujący przypadek:

Pierwszy, o1.compareTo(o2) jest nazywany. card1.getSet() == card2.getSet() tak się składa, że to prawda i tak jest card1.getRarity() < card2.getRarity(), więc zwracasz 1.

Następnie, o2.compareTo(o1) jest nazywany. Znowu, {[1] } to prawda. Następnie przeskakujesz do następującej else, wtedy card1.getId() == card2.getId() jest prawdą, podobnie jak cardType > item.getCardType(). Zwracasz 1 ponownie.

Z tego, o1 > o2 i o2 > o1. Złamałeś kontrakt.

 5
Author: eran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 21:35:58
        if (card1.getRarity() < card2.getRarity()) {
            return 1;

Jeśli jednak card2.getRarity() jest mniejsza niż card1.getRarity() możesz nie wrócić -1.

Podobnie tęsknisz za innymi sprawami. Ja bym to zrobił, można zmienić w zależności od intencji:
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
Author: Joe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-31 08:23:46

Musiałem sortować według kilku kryteriów (Data i, jeśli ta sama data; inne rzeczy...). To, co działało na Eclipse ze starszą wersją Javy, nie działało już na Androidzie : metoda porównywania narusza kontrakt ...

Po przeczytaniu na StackOverflow napisałem oddzielną funkcję, którą wywołałem z compare (), Jeśli daty są takie same. Funkcja oblicza priorytet zgodnie z kryteriami i zwraca -1, 0 LUB 1 do compare (). Wygląda na to, że teraz działa.

 0
Author: Jean Burkhardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-08 07:26:47

Dostałem ten sam błąd z klasą jak poniżej StockPickBean. Wywołane z tego kodu:

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

Po otrzymaniu tego samego błędu:

Java.lang.Nielegalargumentexception: Metoda porównawcza narusza jej ogólną umowę!

Zmieniłem ten wiersz:

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

Do:

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

To naprawia błąd.

 0
Author: pmkent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-09 02:09:15