Java 7 - метод сравнения нарушает его общий контракт!

Казалось, все работает нормально (в течение нескольких дней), но я столкнулся с проблемой только один раз, и мне было очень трудно воспроизвести проблему.

"Метод сравнения нарушает свой общий контракт!" был брошен и полностью застал меня врасплох. У меня есть следующее:

public class CustomComparator implements Comparator<Chromosome> {

public int compare(Chromosome c1, Chromosome c2){

    return c1.compareTo(c2);
}

} 

Мой класс хромосом:

public class Chromosome implements Comparable<Chromosome>{

private double rank;

//bunch of methods...

@Override public int compareTo(Chromosome c){

    final int BEFORE = -1;
    final int EQUAL = 0;
    final int AFTER = 1;

    if (this.getRank() == c.getRank()) //getRank() simply returns a double value 'rank'
        return EQUAL;

    else if (this.getRank() < c.getRank())
            return BEFORE;

    else  //i.e. (this.getRank() > c.getRank())
        return AFTER;   

}

У меня есть ArrayList, и я использовал как Collections.sort(MyList), так и Collections.sort(MyList, Collections.reverseOrder()). Они до сих пор нормально работают. Я просто столкнулся с этой ошибкой только один раз из 100 пробега. Что-то не так с этой реализацией?


person wFateem    schedule 06.12.2014    source источник
comment
Любое из двойных значений NaN?   -  person Jon Skeet    schedule 06.12.2014
comment
Вероятно, результат 0.0/0.0 или что-то в этом роде.   -  person Gábor Bakos    schedule 06.12.2014
comment
Также ваш метод compareTo может быть просто return Double.compare(this.rank, c.rank);. И ваш собственный компаратор бесполезен, поскольку он вызывает compareTo на самих экземплярах хромосомы.   -  person Alexis C.    schedule 06.12.2014
comment
Возможно ли, что ранг NaN? NaN не больше, не меньше и не равно NaN. См. ответы на этот вопрос. для объяснения.   -  person Mike Samuel    schedule 06.12.2014


Ответы (2)


Java 7 немного изменил поведение своих алгоритмов сортировки. Теперь они выдают исключение, если обнаружено нарушение общего контракта для метода compareTo. Вы можете прочитать о свойствах этого контракта, например, здесь.

В общем случае это может быть нарушено, например, в случае, если оно разрешится как a ‹ b и b ‹ a. Если это было обнаружено до Java 7, оно просто молча игнорировалось. Теперь будет выбрано исключение.

Если вы хотите использовать старое поведение, вы можете использовать следующее:

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

Но я не призываю вас делать это. Вам следует просто изменить свою реализацию на стандартную реализацию двойного сравнения через Double.compare(a, b). Эта реализация правильно обрабатывает значения бесконечности и NaN для двойных значений.

Кроме того, если ваш Comparator просто делегирует метод compareTo, его можно вообще отбросить.

person noone    schedule 06.12.2014
comment
Спасибо. Вместо этого я просто использую Double.compare(). Я больше не сталкивался с проблемой, так что это довольно странно. Кроме того, я не уверен, что понимаю приведенный вами пример, который нарушает контракт. Как я могу иметь два двойных значения a и b, такие что a ‹ b и в то же время b ‹ a? - person wFateem; 07.12.2014
comment
@wFateem Это был просто пример того, как это можно было нарушить. На самом деле есть несколько других правил, которые необходимо соблюдать. Например, симметрия. a == a всегда должно быть истинным. Но стандарт IEEE 754, который определяет, как работают двойники, утверждает, что Double.NaN должно быть != Double.NaN, что нарушает правило симметрии. Это одна из вещей, которые исправляет Double.compare(...). - person noone; 07.12.2014
comment
Большое спасибо за разъяснения. Ценить это - person wFateem; 08.12.2014

Возможно, что один из ваших параметров может быть связан с положительной или отрицательной бесконечностью, т.е. делением на ноль. Также не полагайтесь на == для двойных значений. Вы должны просто использовать:

return Double.compare(this.getRank(), c.getRank());
person SMA    schedule 06.12.2014
comment
Спасибо за подсказку о Double.compare(), вероятно, имеет смысл использовать ее вместо этого. Это не может быть проблемой деления на ноль. Ранг — это только значение, возвращаемое нейронной сетью на основе значений пикселей изображения (целое число от 0 до 255). Нейронная сеть использует сигмовидную функцию. - person wFateem; 07.12.2014
comment
Рад, что это помогло. Пожалуйста, закройте этот вопрос, приняв ответ, чтобы он мог помочь другим с аналогичной проблемой. - person SMA; 07.12.2014