背景
記錄下之前調用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方法,這樣會少很多坑。
轉載請標明來源,我的公眾號:哈希同學