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


188

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

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

1
Как называется и класс исключения? Это IllegalArgumentException? Если бы мне пришлось угадывать, я бы подумал, что вы должны делать s1.getParent().equals(s2)вместо s1.getParent() == s2.
Freiheit

И исключение, которое выбрасывается также.
Мэтью Фарвелл

2
Я не знаю много о Java или об API сравнения Java, но этот метод сравнения кажется совершенно неправильным. Предположим s1, что родитель s2, а s2не родитель s1. Тогда compareParents(s1, s2)есть 0, но compareParents(s2, s1)есть 1. Это не имеет смысла. (Кроме того, он не транзитивен, как aix, упомянутый ниже.)
mqp

4
Эта ошибка, по-видимому, создается только определенной библиотекой cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/…
Питер Лори

в Java вы можете использовать равно (вернуть логическое значение) или сравнить (вернуть -1, 0 или +1). Переопределите эти функции в своем классе Foo, и после этого вы можете проверить s1.getParent (). Equals (s2) ...
Mualig

Ответы:


261

Ваш компаратор не является переходным.

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

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

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


3
Введенный в Java 7 java.util.Arrays.sort stackoverflow.com/questions/7849539/…
leonbloy

46
Тот факт, что библиотека обнаруживает это, потрясающий. Кто-то на солнце заслуживает того, чтобы выбросить гиганта. Пожалуйста .
Qix - МОНИКА БЫЛА ПОВРЕЖДЕНА

Можете ли вы обобщить этот ответ немного, чтобы сделать этот вопрос более полезным в качестве справочного поста?
Бернхард Баркер

1
@Qix - насколько я люблю Sun, это было добавлено в Java 7 под баннером Oracle
isapir

1
@isapir Черт! Хороший улов.
Qix - МОНИКА БЫЛА ПОВРЕЖДЕНА

38

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

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

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


7
Я должен добавить, что если кто-то из вас valueявляется NaN (если valueэто doubleили float), он также потерпит неудачу.
Матье

23

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

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

Но это упускает из виду случай, когда ОБА один и два пусты, и в этом случае возвращается неправильное значение (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 );

13

Даже если ваш метод 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;
}   

2
Это было полезно! Мой код был логически в порядке, но произошла ошибка из-за проблемы точности.
JSong

6

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

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

4

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

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 равны нулю.


3

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

Просто вызовите метод checkConsitency со списком параметров и компаратором.
Мартин

Ваш код не компилируется. Классы Compare, Convert(и потенциально другие) не определены. Пожалуйста, обновите фрагмент кода с помощью отдельного примера.
Гили

Вы должны исправить опечатку checkConsi(s)tencyи удалить все избыточные @paramобъявления, чтобы сделать код более читабельным.
Роланд Иллиг

3

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

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

2

Если compareParents(s1, s2) == -1тогдаcompareParents(s2, s1) == 1 ожидается. С вашим кодом это не всегда так.

Конкретно если s1.getParent() == s2 && s2.getParent() == s1. Это всего лишь одна из возможных проблем.


1

Редактирование конфигурации VM работало для меня.

-Djava.util.Arrays.useLegacyMergeSort=true

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

2
Также, пожалуйста, объясните, как это помогает с описанной проблемой. В настоящее время это практически только кодовый ответ.
Юннощ

0

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


Нет, на самом деле, я думаю, что OP пытается отсортировать какой-то список и хочет на самом деле сравнить ссылки.
Эдвард Фальк,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.