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 източник
comment
Не съм сигурен, че това ще го поправи, но просто бих направил return lhs.getDist().compareTo(rhs.getDist()); docs.oracle.com/javase/6/docs/api/java/lang/   -  person Ken Wolf    schedule 15.07.2013
comment
stackoverflow.com/questions/8327514/   -  person Dave Newton    schedule 15.07.2013
comment
Има ли възможност Thing.getDist() да модифицира Thing?   -  person Ted Hopp    schedule 15.07.2013
comment
Здравей Тед - не, това не е възможно. Това е просто прост гетер.   -  person DiscDev    schedule 15.07.2013


Отговори (2)


Няма смисъл да изобретяваме колелото. Вярвам, че трябва просто да върнете lhs.getDist().compareTo(rhs.getDist()); и да оставите предоставената реализация compareTo свърши работата.

Сравнява числено два обекта Double.

Има два начина, по които сравненията, извършени с този метод, се различават от тези, извършени от операторите за числово сравняване на езика Java (‹, ‹=, ==, >=, >), когато се прилагат към примитивни двойни стойности:

  1. Double.NaN се счита от този метод за равен на себе си и по-голям от всички други двойни стойности (включително 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, така че няма възможност да сравнява препратки към обекти, в противен случай резултатите, които получавам от сравнението, ще бъдат непостоянни. Мисля, че Кен Улф и Новият идиот са на път за нещо... - person DiscDev; 15.07.2013
comment
@spotdog13 Операторът ‹ ще разопакова обектите Double за сравнение, но операторът == ще сравни препратките към обекти. Обектите винаги трябва да се сравняват с .equals(). - person Michael Krussel; 15.07.2013
comment
Ах, да, Майкъл, прав си. Знаех, че ‹ ще ги разопакова...всъщност е много малко вероятно getDist() някога да бъде еднакъв за 2 обекта поради прецизността, включена в стойността...това вероятно би обяснило защо това сортира нещата правилно и аз пропуснах моя правописна грешка :). Всъщност не разбрах, че getDist() е Double (мислех, че е double, но това е стойност, извлечена от DB, която според мен изискваше да избера Double по времето, когато създадох таблицата), докато не зададох този въпрос. - person DiscDev; 15.07.2013
comment
Използването на == вероятно е коренът на проблема. Възможно е Android 4.0+ да е променил стратегиите за това колко често исканите Double стойности се кешират (и следователно се считат за ==), което би обяснило промяната в поведението на вашия код. - person Ted Hopp; 15.07.2013