«Метод сравнения нарушает его общий контракт!»

Может кто-нибудь объяснить мне простыми словами, почему этот код генерирует исключение: «Метод сравнения нарушает его общий контракт!» И как его исправить?

private int compareParents(Foo s1, Foo s2) { if (s1.getParent() == s2) return -1; if (s2.getParent() == s1) return 1; return 0; } 

Ваш компаратор не транзитивен.

Пусть A – родительский элемент B , а B – родительский элемент C Так как A > B и B > C , то это должно быть так, что A > C Однако, если ваш компаратор вызывается на A и C , он будет возвращать ноль, что означает A == C Это нарушает договор и, следовательно, исключает исключение.

Довольно приятно, чтобы библиотека обнаружила это и сообщила вам, а не вела себя беспорядочно.

Один из способов удовлетворить требование транзитивности в compareParents() состоит в том, чтобы пересечь цепочку getParent() вместо того, чтобы смотреть только на ближайшего предка.

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

 if (value < other.value) return -1; else if (value >= other.value) return 1; else return 0; 

value >= other.value должно (очевидно) фактически быть value > other.value чтобы вы могли фактически вернуть 0 с равными объектами.

Нарушение контракта часто означает, что компаратор не обеспечивает правильное или согласованное значение при сравнении объектов. Например, вам может потребоваться выполнить сравнение строк и форсировать пустые строки для сортировки до конца с помощью:

 if ( one.length() == 0 ) { return 1; // empty string sorts last } if ( two.length() == 0 ) { return -1; // empty string sorts last } return one.compareToIgnoreCase( two ); 

Но это не учитывает случай, когда BOTH один и два пустые – и в этом случае возвращается неправильное значение (1 вместо 0, чтобы показать совпадение), и компаратор сообщает об этом как о нарушении. Он должен был быть написан как:

 if ( one.length() == 0 ) { if ( two.length() == 0 ) { return 0; // BOth empty - so indicate } return 1; // empty string sorts last } if ( two.length() == 0 ) { return -1; // empty string sorts last } return one.compareToIgnoreCase( two ); 

Даже если ваш compareTo обладает транзитивностью в теории, иногда тонкие ошибки обманывают … например, арифметическую ошибку с плавающей запятой. Это произошло со мной. это был мой код:

 public int compareTo(tfidfContainer compareTfidf) { //descending order if (this.tfidf > compareTfidf.tfidf) return -1; else if (this.tfidf < compareTfidf.tfidf) return 1; else return 0; } 

Транзитивное свойство явно имеет место, но по какой-то причине я получаю исключение IllegalArgumentException. И получается, что из-за крошечных ошибок в арифметике с плавающей запятой, ошибки округления, в результате чего переходное свойство ломается там, где они не должны! Поэтому я переписал код, чтобы рассмотреть действительно крошечные отличия 0, и это сработало:

 public int compareTo(tfidfContainer compareTfidf) { //descending order if ((this.tfidf - compareTfidf.tfidf) < .000000001) return 0; if (this.tfidf > compareTfidf.tfidf) return -1; else if (this.tfidf < compareTfidf.tfidf) return 1; return 0; } 

В нашем случае была получена эта ошибка, потому что мы случайно перевернули порядок сравнения s1 и s2. Поэтому следите за этим. Это было явно более сложным, чем следующее, но это иллюстрация:

 s1 == s2 return 0; s2 > s1 return 1; s1 < s2 return -1; 

Java не проверяет согласованность в строгом смысле слова, а только уведомляет вас, если у нее возникают серьезные проблемы. Кроме того, это не дает вам много информации об ошибке.

Я был озадачен тем, что происходит в моем сортировщике, и сделал строгий консистентер, возможно, это поможет вам:

 /** * @param dailyReports * @param comparator */ public static  void checkConsitency(final List dailyReports, final Comparator comparator) { final Map> objectMapSmallerOnes = new HashMap>(); iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback() { /** * @param o1 * @param o2 */ @Override public void pair(T o1, T o2) { final int diff = comparator.compare(o1, o2); if (diff < Compare.EQUAL) { checkConsistency(objectMapSmallerOnes, o1, o2); getListSafely(objectMapSmallerOnes, o2).add(o1); } else if (Compare.EQUAL < diff) { checkConsistency(objectMapSmallerOnes, o2, o1); getListSafely(objectMapSmallerOnes, o1).add(o2); } else { throw new IllegalStateException("Equals not expected?"); } } }); } /** * @param objectMapSmallerOnes * @param o1 * @param o2 */ static  void checkConsistency(final Map> objectMapSmallerOnes, T o1, T o2) { final List smallerThan = objectMapSmallerOnes.get(o1); if (smallerThan != null) { for (final T o : smallerThan) { if (o == o2) { throw new IllegalStateException(o2 + " cannot be smaller than " + o1 + " if it's supposed to be vice versa."); } checkConsistency(objectMapSmallerOnes, o, o2); } } } /** * @param keyMapValues * @param key * @param  * @param  * @return List */ public static  List getListSafely(Map> keyMapValues, Key key) { List values = keyMapValues.get(key); if (values == null) { keyMapValues.put(key, values = new LinkedList()); } return values; } /** * @author Oku * * @param  */ public interface IPairIteratorCallback { /** * @param o1 * @param o2 */ void pair(T o1, T o2); } /** * * Iterates through each distinct unordered pair formed by the elements of a given iterator * * @param it * @param callback */ public static  void iterateDistinctPairs(final Iterator it, IPairIteratorCallback callback) { List list = Convert.toMinimumArrayList(new Iterable() { @Override public Iterator iterator() { return it; } }); for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) { for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) { callback.pair(list.get(outerIndex), list.get(innerIndex)); } } } 

Я видел, как это происходило в fragmentе кода, где выполнялась часто повторяющаяся проверка нулевых значений:

 if(( A==null ) && ( B==null ) return +1;//WRONG: two null values should return 0!!! 

В моем случае я делал что-то вроде следующего:

 if (a.someField == null) { return 1; } if (b.someField == null) { return -1; } if (a.someField.equals(b.someField)) { return a.someOtherField.compareTo(b.someOtherField); } return a.someField.compareTo(b.someField); 

То, что я забыл проверить, было тогда, когда и a.someField, и b.someField имеют значение null.

Вы не можете сравнивать данные объекта следующим образом: s1.getParent() == s2 – это будет сравнивать ссылки на объекты. Вы должны переопределить equals function для classа Foo, а затем сравнить их как s1.getParent().equals(s2)

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