Collections.sort()報Comparison method violates its general contract異常解決方法

背景

記錄下之前調用Collections.sort()造成App Crash的例子。業務原因,需要在主App中的文件進行排序,排序的規則是按照最近的修改時間升序排序,然后刪除修改時間較小的文件列表,實現簡單的清緩存功能。但是簡單的實現后,注解拋出一個java.lang.IllegalArgumentException:Comparison method violates its general contract!。

哈哈 讓我們一起搖擺!

分析問題

問題代碼如下:

 Collections.sort(child, new Comparator<File>() {
        @Override
        public int compare(File lFile, File rFile) {
            Long lModified = lFile.lastModified();
            Long rModified = rFile.lastModified();
            return lModified.compareTo(rModified);
        }
    });

是不是乍一看,覺得代碼寫的絲毫問題都沒有,是的,剛開始我也是這么覺得。但是這篇文章你接著往下看,就知道哪里出了問題了。

說到Collections.sort()和java.lang.IllegalArgumentException:Comparison method violates its general contract!這個崩潰,相信大家都已經百度過大概因為什么原因了。沒錯,Collections.sort()在JDK6和JDK7中實現的底層排序算法變了,在JDK6中使用的時MergeSort排序,而在JDK7中使用的是TimSort。里面具體的算法自行百度吧,我是實在沒看懂里面咋實現的,但是這個傳說中的TimSort排序算法對比較大小的要求更高了:

比較器Comparator要求:

1 sgn(compare(x, y)) == -sgn(compare(y, x))
2 ((compare(x, y)>0) && (compare(y, z)>0))
3 如果compare(x, y)==0 那么sgn(compare(x, z))==sgn(compare(y, z))

舉個例子,比如有下面的代碼:

 Collections.sort(child, new Comparator<Integer>() {
        @Override
        public int compare(Integer l, Integer r) {
            return l > r ? 1 : -1;
        }
    });

恭喜你,crash了。
為什么呢,因為這里面就違反了自反性第一個規則,比如l的值是1,r的值也是1,那么compare(l,r)和compare(r,l)的結果是不一樣的,于是TimSort就會檢測到這種異常,就GG了。

但是!但是!

前面說的這段代碼:

 Collections.sort(child, new Comparator<File>() {
        @Override
        public int compare(File lFile, File rFile) {
            Long lModified = lFile.lastModified();
            Long rModified = rFile.lastModified();
            return lModified.compareTo(rModified);
        }
    });

有什么問題呢?調用的都是SDK內部實現的compareTo,其實吧,這里面確實是沒有問題的,但是這里面忽視了一種情況:

File為null的情況!

File為null的情況!

File為null的情況!

那么你就會說,我可以保證這里面的File文件都是非空的啊,我的代碼可以保證啊,而且也沒有報NullPointException異常呀,為什么要考慮File為null的情況呢。

因為原因很簡單,JVM并不知道.就是這么簡單粗暴,因為JVM對你的代碼是無感知的,它無法感知File文件是否一定非空,所以JVM就會在假設File為null的時候,無法判定比較的正確性,然后拋出異常出來。

解決問題

知道原因,那么解決方法就是對File為null或File不存在的情況進行下兼容處理,處理后的代碼如下:

    Collections.sort(child, new Comparator<File>() {
        @Override
        public int compare(File lFile, File rFile) {
            boolean lInValid = (lFile == null || !lFile.exists());
            boolean rInValid = (rFile == null || !rFile.exists());
            boolean bothInValid = lInValid && rInValid;
            if (bothInValid) {
                return 0;
            }

            if (lInValid) {
                return -1;
            }

            if (rInValid) {
                return 1;
            }

            Long lModified = lFile.lastModified();
            Long rModified = rFile.lastModified();
            return lModified.compareTo(rModified);
        }
    });

然后問題完美解決。

總結

java.lang.IllegalArgumentException:Comparison method violates its general contract!這個異常確實很坑,在使用Collections.sort排序時,很容易拋異常,所以只能在寫里面排序邏輯的時候,小心小心再小心,如果可能的話, 最好使用SDK內部實現的compareTo方法,這樣會少很多坑。

轉載請標明來源,我的公眾號:哈希同學

image
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容