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