Ошибка Java: метод сравнения нарушает его общий контракт


Я видел много вопросов об этом, и попытался решить эту проблему, но после одного часа гуглить и много проб и ошибок, я все еще не могу это исправить. Я надеюсь, что некоторые из вас поймут проблему.

вот что я получил:

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

а это мой компаратор:

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

есть идеи?

7 52

7 ответов:

сообщение об исключении на самом деле довольно описательно. Контракт, который он упоминает,транзитивность: если A > B и B > C тогда для любого A,B и C:A > C. Я проверил его с помощью бумаги и карандаша, и ваш код, кажется, имеет несколько отверстий:

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

вы не вернетесь -1 если card1.getRarity() > card2.getRarity().


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

вы возвращаетесь -1 если идентификаторы не равны. Вы должны вернуться -1 или 1 в зависимости от того, какой идентификатор был больший.


взгляните на это. Помимо того, что он гораздо более читаем, Я думаю, что он должен действительно работать:

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!

это также имеет какое-то отношение к версии JDK. Если он хорошо работает в JDK6, возможно, у него будет проблема в JDK 7, описанная вами, потому что метод реализации в jdk 7 был изменен.

взгляните на это:

описание: алгоритм сортировки используется java.util.Arrays.sort и (косвенно)java.util.Collections.sort была заменена. Новая реализация сортировки может выбросить IllegalArgumentException если он обнаруживает Comparable, что нарушает Comparable контракт. Предыдущая реализация молча проигнорировал такую ситуацию. Если требуется предыдущее поведение, можно использовать новое системное свойство,java.util.Arrays.useLegacyMergeSort, чтобы восстановить предыдущее поведение mergesort.

Я не знаю точной причины. Однако, если вы добавляете код перед использованием сортировки. Все будет хорошо.

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

вы можете использовать следующий класс для определения ошибок транзитивности в ваших Компараторах:

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

просто вызовите Comparators.verifyTransitivity(myComparator, myCollection) перед кодом, который не работает.

рассмотрим следующий случай:

первый, o1.compareTo(o2) называется. card1.getSet() == card2.getSet() случается и так, и так card1.getRarity() < card2.getRarity(), Так что вы вернуть 1.

затем, o2.compareTo(o1) называется. Опять,card1.getSet() == card2.getSet() - Это правда. Затем вы переходите к следующему else, потом card1.getId() == card2.getId() оказывается, это правда, и так cardType > item.getCardType(). Вы возвращаете 1 раз.

от этого o1 > o2 и o2 > o1. Ты нарушил контракт.

        if (card1.getRarity() < card2.getRarity()) {
            return 1;
, если card2.getRarity() меньше card1.getRarity() вы можете не вернуться -1.

вы также пропустите другие случаи. Я бы сделал это, вы можете изменить вокруг в зависимости от вашего намерения:

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;

    }
}

Мне пришлось Сортировать по нескольким критериям (дата, и, если та же дата; другие вещи...). То, что работало на Eclipse со старой версией Java, больше не работало на Android : метод сравнения нарушает контракт ...

после чтения на StackOverflow, я написал отдельную функцию, которую я вызвал из compare (), если даты совпадают. Эта функция вычисляет приоритет, в соответствии с критериями и возвращает -1, 0 или 1, чтобы сравнить(). Кажется, теперь это работает.

Я получил ту же ошибку С класс вроде следующего StockPickBean. Вызывается из этого кода:

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

после получения той же ошибки:

java.ленг.IllegalArgumentException: метод сравнения нарушает его генеральный контракт!

Я изменил эту строку:

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

to:

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

это исправляет ошибку.