"Метод сравнения нарушает его общий контракт!"
Может кто-нибудь объяснить мне простыми словами, почему этот код генерирует исключение, "Метод сравнения нарушает его общий контракт!" и как его исправить?
private int compareParents(Foo s1, Foo s2) {
if (s1.getParent() == s2) return -1;
if (s2.getParent() == s1) return 1;
return 0;
}
Ответы
Ответ 1
Ваш компаратор не транзитивен.
Пусть A
- родительский элемент B
, а B
- родительский элемент C
. Поскольку A > B
и B > C
, то это должно быть так, что A > C
. Однако, если ваш компаратор вызывается на A
и C
, он будет возвращать ноль, что означает A == C
. Это нарушает контракт и, следовательно, исключает исключение.
Это довольно хорошо из библиотеки, чтобы обнаружить это и сообщить вам, а не вести себя беспорядочно.
Один из способов удовлетворить требование транзитивности в compareParents()
состоит в том, чтобы пересечь цепочку getParent()
вместо того, чтобы смотреть только на непосредственного предка.
Ответ 2
Просто потому, что это то, что я получил, когда я искал эту ошибку, моя проблема заключалась в том, что у меня был
if (value < other.value)
return -1;
else if (value >= other.value)
return 1;
else
return 0;
value >= other.value
должен (очевидно) фактически быть value > other.value
, чтобы вы могли фактически вернуть 0 с равными объектами.
Ответ 3
Нарушение контракта часто означает, что компаратор не обеспечивает правильное или согласованное значение при сравнении объектов. Например, вам может потребоваться выполнить сравнение строк и принудительные пустые строки для сортировки до конца с помощью:
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 );
Ответ 4
Даже если ваш 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;
}
Ответ 5
В нашем случае была получена эта ошибка, потому что мы случайно перевернули порядок сравнения s1 и s2. Поэтому следите за этим. Это было явно более сложным, чем следующее, но это иллюстрация:
s1 == s2
return 0;
s2 > s1
return 1;
s1 < s2
return -1;
Ответ 6
Я видел, как это происходило в фрагменте кода, где выполнялась часто повторяющаяся проверка нулевых значений:
if(( A==null ) && ( B==null )
return +1;//WRONG: two null values should return 0!!!
Ответ 7
Java не проверяет согласованность в строгом смысле слова, а только уведомляет вас, если у нее возникают серьезные проблемы. Также он не дает вам много информации об ошибке.
Я был озадачен тем, что происходило в моем сортировщике, и сделал строгий консистентер, возможно, это поможет вам:
/**
* @param dailyReports
* @param comparator
*/
public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
/**
* @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 <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
final List<T> 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 supposed to be vice versa.");
}
checkConsistency(objectMapSmallerOnes, o, o2);
}
}
}
/**
* @param keyMapValues
* @param key
* @param <Key>
* @param <Value>
* @return List<Value>
*/
public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
List<Value> values = keyMapValues.get(key);
if (values == null) {
keyMapValues.put(key, values = new LinkedList<Value>());
}
return values;
}
/**
* @author Oku
*
* @param <T>
*/
public interface IPairIteratorCallback<T> {
/**
* @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 <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
@Override
public Iterator<T> 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));
}
}
}
Ответ 8
В моем случае я делал что-то вроде следующего:
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.
Ответ 9
Если compareParents(s1, s2) == -1
тогда compareParents(s2, s1) == 1
. С вашим кодом это не всегда так.
В частности, если s1.getParent() == s2 && s2.getParent() == s1
. Это всего лишь одна из возможных проблем.
Ответ 10
Вы не можете сравнивать данные объекта следующим образом: s1.getParent() == s2
- это будет сравнивать ссылки на объекты. Вы должны переопределить equals function
для класса Foo, а затем сравнить их как это s1.getParent().equals(s2)
Ответ 11
Редактирование конфигурации VM работало для меня.
-Djava.util.Arrays.useLegacyMergeSort=true