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

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

Это то, что я получаю:

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()  item.getCardType()) { return 1; } else { if (cardType == item.getCardType()) { return 0; } return -1; } } return -1; } } return 1; } } 

Есть идеи?

Сообщение об исключении на самом деле довольно наглядное. Контракт, который он упоминает, является транзитивностью : если 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 , был заменен. Новая реализация sort может IllegalArgumentException если оно обнаруживает Comparable которое нарушает договор Comparable . Предыдущая реализация молча игнорировала такую ​​ситуацию. Если требуется предыдущее поведение, вы можете использовать новое системное свойство java.util.Arrays.useLegacyMergeSort , чтобы восстановить предыдущее поведение слияния.

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

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

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

 /** * @author Gili Tzabari */ public final class Comparators { /** * Verify that a comparator is transitive. * * @param  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  void verifyTransitivity(Comparator comparator, Collection 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() - true. Затем вы переходите к следующему, а затем card1.getId() == card2.getId() имеет значение true, а также 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 для сравнения (). Кажется, теперь это работает.

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

 List beansListcatMap.getValue(); beansList.sort(StockPickBean.Comparators.VALUE); public class StockPickBean implements Comparable { 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 VALUE = (val1, val2) -> (int) (val1.value - val2.value); } } 

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

java.lang.IllegalArgumentException: метод сравнения нарушает общий контракт!

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

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

чтобы:

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

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

Interesting Posts

Вернуть файл в ASP.NET Core Web API

Мой источник питания скулит, я могу что-нибудь сделать с этим?

Блокировать запросы ARP (или, если возможно, широковещательное сообщение) из A SPECIFIC HOST в подсети

Как я могу сделать запуск Emacs быстрее?

Автоматическое добавление / завершение из текстового файла в поле редактирования delphi

Почему внедрение шаблона Singleton в Java-коде (иногда) считается анти-шаблоном в Java-мире?

Преобразовать фиксированную ширину в CSV?

Почему System.Transactions TransactionScope default Isolationlevel Serializable

Сжатие раздела exFAT

Проблема с кодировкой Java 8 UTF-8 (ошибка java?)

Использование хеша MD5 в строке в cocoa?

Java: как найти элемент через строку xpath на org.w3c.dom.document

Почему мой хеш командной строки отличается от хеш-результатов онлайн MD5?

Параметр JSON весной MVC-controller

Как я могу изменить учетные данные по умолчанию, используемые для подключения к Visual Studio Online (TFSPreview) при загрузке Visual Studio?

Давайте будем гением компьютера.