Ошибка 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 ответов:
сообщение об исключении на самом деле довольно описательно. Контракт, который он упоминает,транзитивность: если
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);
это исправляет ошибку.