1. 程式人生 > >jdk7 Collections.sort()方法報錯分析

jdk7 Collections.sort()方法報錯分析

問題背景

起因

前些天測試給提了一個專案裡的bug,在檢視專案的一個線上資料的列表的時候發生了崩潰.然後我根據bugly定位發現是在使用Collection.sort()對list排序的時候產生Comparison method violates its general contract異常.但是Collection.sort()在JDK1.6中並沒有出現過這樣的異常啊…

問題定位

首先我懷著很納悶的心情看了下bugly的崩潰日誌.有這麼一段日誌資訊


11-23 14:02:55.357 27729 32217 W System.err: java.lang.IllegalArgumentException: Comparison method violates its general contract!
11
-23 14:02:55.357 27729 32217 W System.err: at java.util.TimSort.mergeLo(TimSort.java:743) 11-23 14:02:55.357 27729 32217 W System.err: at java.util.TimSort.mergeAt(TimSort.java:479) 11-23 14:02:55.357 27729 32217 W System.err: at java.util.TimSort.mergeCollapse(TimSort.java:406) 11-23 14:02:55.357 27729 32217
W System.err: at java.util.TimSort.sort(TimSort.java:210) 11-23 14:02:55.357 27729 32217 W System.err: at java.util.TimSort.sort(TimSort.java:169) 11-23 14:02:55.357 27729 32217 W System.err: at java.util.Arrays.sort(Arrays.java:2010) 11-23 14:02:55.358 27729 32217 W System.err: at java.util.Collections.sort(Collections.java:1883
)

然後我定位到出錯的程式碼

if (list != null && list.size() > 0){
        //降序排序
        Collections.sort(list, new Comparator<Spo2Result>() {
            @Override
            public int compare(Spo2Result lhs, Spo2Result rhs) {
                return lhs.getMeasureTime()>rhs.getMeasureTime() ? -1 : 1;
            }

        });
    }

丟擲異常的地方是對一個list按照測量時間降序排序,邏輯很簡單.然後我仔仔細細前前後後裡裡外外的看了好幾遍這段程式碼,為了驗證程式碼的可靠性,我還特意z針對這個排序方法寫了一個測試案例,如下:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestSortError {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        List<Integer> l = new ArrayList<Integer>();
        l.add(1);
        l.add(3);
        l.add(2);
        l.add(2);
        l.add(2);
        l.add(2);
        l.add(5);
        l.add(6);
        l.add(4);
        l.add(1);
        testSort(l);
    }

    private static void testSort(List<Integer> l) {

        Collections.sort(l, new Comparator<Integer>() {

            public int compare(Integer o1, Integer o2) {
                // TODO Auto-generated method stub
                return o1 > o2 ? -1 : 1;
            }
        });

        for (int i = 0; i < l.size(); i++) {
            System.out.println(l.get(i));
        }

    }
}

然後輸出:

6
5
4
3
2
2
2
2
1
1

問題分析

並沒有出現自己腦海中的崩潰現象…於是只能查這個異常,找到了一個有關JDK不相容的宣告.

這裡寫圖片描述

這個的意思大概是說JDK比較器的排序演算法更換了實現方法,新的比較器在違背規則的情況下有可能丟擲異常.看到這個就可以確定大概的原因了,因為專案中的排序程式碼違背了比較器的規則.

於是google了一下比較器的規則:

Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.In the foregoing description, the notation sgn(expression) designates the mathematical signum function, which is defined to return one of -1, 0, or 1 according to whether the value ofexpression is negative, zero or positive. The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.) The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0. Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z. It is generally the case, but not strictly required that (compare(x, y)==0) == (x.equals(y)). Generally speaking, any comparator that violates this condition should clearly indicate this fact. The recommended language is “Note: this comparator imposes orderings that are inconsistent with equals.”

即滿足三個規則:
1. 確保:sgn(compare(x, y)) == -sgn(compare(y, x)).
2. 確保:如果((compare(x, y)>0) && (compare(y, z)>0)),那麼compare(x, z)>0.
3. 確保:如果compare(x, y)==0,那麼對於任意的z都有sgn(compare(x, z))==sgn(compare(y, z))成立.

問題解決

程式碼中出現的三目運算子明顯違背了比較器規則,所以把程式碼稍微改了下,增加了相等情況的判斷.

if (list != null && list.size() > 0){
        //降序排序
        Collections.sort(list, new Comparator<Spo2Result>() {
            @Override
            public int compare(Spo2Result lhs, Spo2Result rhs) {
                return lhs.getMeasureTime()==rhs.getMeasureTime()
                        ?0:(lhs.getMeasureTime()>rhs.getMeasureTime() ? -1 : 1);
            }

        });
    }

總結

使用三目運算子要注意一定要判斷相等的情況.否則會違背比較器的規則.