Android – метод сравнения нарушает общий контракт

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

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

Вот мой метод сравнения:

            Collections.sort(collectionOfThings, new Comparator<Thing>()
            {
                public int compare(Thing lhs, Thing rhs) 
                {
                    //getDist() returns a Double with a capital D...perhaps that has something to do with it?
                    if(lhs.getDist() < rhs.getDist())
                    {
                        return -1;
                    }
                    if(lhs.getDist() == rhs.getDist())
                    {
                        return 0;
                    }

                    return 1;
                };
            });

Вот исключение:

Caused by: java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.TimSort.mergeLo(TimSort.java:743)
    at java.util.TimSort.mergeAt(TimSort.java:479)
    at java.util.TimSort.mergeCollapse(TimSort.java:404)
    at java.util.TimSort.sort(TimSort.java:210)
    at java.util.TimSort.sort(TimSort.java:169)
    at java.util.Arrays.sort(Arrays.java:2038)
    at java.util.Collections.sort(Collections.java:1891)

Кажется, ограничен Android 4.0+. Любая помощь приветствуется.


person DiscDev    schedule 15.07.2013    source источник


Ответы (2)


Бесполезно изобретать велосипед. Я считаю, что вы должны просто вернуть lhs.getDist().compareTo(rhs.getDist()); и позволить предоставленной реализации compareTo выполнить задание .

Численно сравнивает два объекта Double.

Есть два способа, которыми сравнения, выполняемые этим методом, отличаются от тех, которые выполняются операторами числового сравнения языка Java (‹, ‹=, ==, >=, >) при применении к примитивным значениям типа double:

  1. Double.NaN считается этим методом равным самому себе и большим, чем все другие значения типа double (включая Double.POSITIVE_INFINITY).

  2. 0.0d в этом методе считается большим, чем -0.0d.

Это гарантирует, что естественный порядок объектов Double, налагаемый этим методом, согласуется с равными.

Я полагаю, что вы получили это исключение, потому что ваша текущая реализация может быть неспособна работать со значениями Double.NaN и positive/negative zero, и при этом соблюдать общий контракт. Посмотрите на OpenJDK Double#compare(double,double) исходный код:

public static int More ...compare(double d1, double d2) {
   if (d1 < d2)
        return -1;           // Neither val is NaN, thisVal is smaller
    if (d1 > d2)
        return 1;            // Neither val is NaN, thisVal is larger

    long thisBits = Double.doubleToLongBits(d1);
    long anotherBits = Double.doubleToLongBits(d2);

    return (thisBits == anotherBits ?  0 : // Values are equal
            (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
             1));                          // (0.0, -0.0) or (NaN, !NaN)
}

Также просмотрите документацию Double#equals()

Обратите внимание, что в большинстве случаев для двух экземпляров класса Double, d1 и d2, значение d1.equals(d2) истинно тогда и только тогда, когда d1.doubleValue() == d2.doubleValue()

также имеет значение true. Однако есть два исключения:

Если d1 и d2 оба представляют Double.NaN, то метод equals возвращает значение true, даже если Double.NaN==Double.NaN имеет значение false. Если d1 представляет собой +0,0, а d2 представляет собой -0,0, или наоборот, проверка на равенство имеет значение false, даже если +0,0==-0,0 имеет значение true.

person AllTooSir    schedule 15.07.2013
comment
Привет - спасибо за ответ. У меня есть ощущение, что вы правы, но на самом деле нет причин, по которым getDist() должен когда-либо быть NAN или бесконечностью. Значение getDist устанавливается для каждого элемента в коллекции непосредственно перед вызовом сортировки. Я собираюсь попробовать изменить метод сравнения, как вы предложили, но, к сожалению, поскольку я не могу воспроизвести эту ошибку локально, мне придется выпустить ее в Play Store и посмотреть, прекратятся ли отчеты об ошибках. Я приму этот ответ, как только узнаю, сработало ли это. Спасибо! - person DiscDev; 15.07.2013
comment
Кроме того, я полностью согласен - бесполезно изобретать велосипед - я думаю, я просто настолько привык писать свои собственные компараторы для пользовательских объектов, что забыл, что уже существуют компараторы для встроенной библиотеки объектов. Иногда я слишком углубляюсь в собственный код, чтобы помнить такие вещи :) - person DiscDev; 15.07.2013
comment
Обратите внимание, что POSITIVE_INFINITY и NEGATIVE_INFINITY не должны вызывать проблем при использовании операторов числового сравнения. Это просто NaN и положительные/отрицательные нулевые случаи, которые были бы проблематичными. - person Ted Hopp; 15.07.2013

Вместо того, чтобы сравнивать два объекта Double, вам следует сравнивать их значения (getDoubleValue()). Сравнение двух объектов не обязательно означает, что их значения равны.

person Alex Fu    schedule 15.07.2013
comment
Алекс, я думал, что java будет достаточно умным, чтобы сравнивать двойные значения... этот метод сравнения отлично работает на старых устройствах Android, поэтому нет никакой возможности, чтобы он сравнивал ссылки на объекты, иначе результаты, которые я получаю от сравнения, будут ошибочными. Я думаю, что Кен Вульф и The New Idiot что-то замышляют... - person DiscDev; 15.07.2013
comment
@spotdog13 Spotdog13 Оператор ‹ распаковывает объекты Double для сравнения, а оператор == сравнивает ссылки на объекты. Объекты всегда следует сравнивать с .equals(). - person Michael Krussel; 15.07.2013
comment
Ах да, Михаил, вы правы. Я знал, что ‹ распаковывает их... на самом деле очень маловероятно, что getDist() когда-либо будет одинаковым для двух объектов из-за точности, связанной со значением... это, вероятно, объясняет, почему это правильно сортирует вещи, и я пропустил свой опечатка :). На самом деле я не осознавал, что getDist() был двойным (думал, что это двойной, но это значение, извлеченное из БД, которое, как я полагаю, требовало от меня выбора Double во время создания таблицы), пока я не задал этот вопрос. - person DiscDev; 15.07.2013
comment
Использование ==, вероятно, является корнем проблемы. Возможно, в Android 4.0+ изменились стратегии того, как часто запрашиваемые значения Double кэшируются (и, следовательно, считаются ==), что объясняет изменение поведения вашего кода. - person Ted Hopp; 15.07.2013