java中排序報:Comparison method violates its general contract異常的解決
前言
上周線上的一段排序的java代碼出現(xiàn)了一個Comparison method violates its general contract
,在解決這個問題的途中學(xué)到了一些知識這里總結(jié)分享一下。
異常原因
這個排序?qū)е碌漠惓趈ava7以上的版本出現(xiàn),所以如果你的JDK從6升級到了7或者8,那一定要小心此異常。
在java7的兼容列表中,就有對此排序不兼容的說明:
Area: API: Utilities Synopsis: Updated sort behavior for Arrays and Collections may throw an IllegalArgumentException Description: The sorting algorithm used by java.util.Arrays.sort and (indirectly) by java.util.Collections.sort has been replaced. The new sort implementation may throw an IllegalArgumentException if it detects a Comparable that violates the Comparable contract. The previous implementation silently ignored such a situation. If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort, to restore previous mergesort behavior. Nature of Incompatibility: behavioral RFE: 6804124
我從資料中查閱到j(luò)ava7開始引入了Timsort的排序算法。我之前一直以為大部分標(biāo)準(zhǔn)庫的內(nèi)置排序算法都是快速排序。現(xiàn)在才得知很多語言內(nèi)部都使用Timsort排序。隨后我在wiki百科上找到了這樣一句話:
t was implemented by Tim Peters in 2002 for use in the Python programming language.
所以這個排序自然是以他命名的。
隨后我又在網(wǎng)上找到了這樣一張圖排序比較的圖:
可以發(fā)現(xiàn),Timsort在表現(xiàn)上比QuickSort還要好。
這篇博客不去詳細(xì)討論Timsort的實(shí)現(xiàn)(看上去這個算法還挺復(fù)雜的),我可能會寫另一篇博客單獨(dú)討論Timsort,簡單來說Timsort結(jié)合了歸并排序和插入排序。這個算法在實(shí)現(xiàn)過程中明確需要:嚴(yán)格的單調(diào)遞增或者遞減來保證算法的穩(wěn)定性。
sgn(compare(x, y)) == -sgn(compare(y, x))
((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0
compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z
看上去很像離散數(shù)學(xué)課中學(xué)習(xí)的集合的對稱性,傳遞性的關(guān)系。
所以異常的原因是因?yàn)榕判蛩惴ú粔驀?yán)謹(jǐn)導(dǎo)致的,實(shí)際上業(yè)務(wù)上的代碼經(jīng)常不如純技術(shù)上的嚴(yán)謹(jǐn)。比如對于這樣一個算法:
選出航班中的最低價
那如果兩個相等低價同時存在,按照尋找最低價的邏輯如果這么寫:
if (thisPrice < lowPrice){ lowPrice = thisPrice; }
那低價這個位置就是“先到先得”了。
但如果這么實(shí)現(xiàn):
if(thisPrice <= lowPrice){ lowPrice = thisPrice; }
那后面的低價就會覆蓋前面的,變成了“后來者居上”。編程中經(jīng)常遇到先到先得和后來者居上這兩個問題。
所以對于上面那個需要提供嚴(yán)謹(jǐn)?shù)呐袛啻笮”容^函數(shù)實(shí)現(xiàn)。所以如果是這樣的:
return x > y ? 1 : -1;
那么就不符合此條件。
不過我們邏輯要比這個復(fù)雜,其實(shí)是這樣一個排序條件。按照:
- 價格進(jìn)行排序,如果價格相等則起飛時間靠前的先排。
- 如果起飛時間也相等,就會按照:
- 非共享非經(jīng)停>非經(jīng)停>非共享>經(jīng)停的屬性進(jìn)行優(yōu)先級選擇,如果這些屬性都全部相等,才只能算是相等了。
所以這個判斷函數(shù)的問題是:
public compareFlightPrice(flightPrice o1, flightPrice o2){ // 非經(jīng)停非共享 if (o1.getStopNumber() == 0 && !o1.isShare()) { return -1; } else if (o2.getStopNumber() == 0 && !o2.isShare()) { return 1; } else { if (o1.getStopNumber() == 0) { return -1; } else if (o2.getStopNumber() == 0) { return 1; } else { if (!o1.isShare()) { return -1; } else if (!o2.isShare()) { return 1; } else { if (o1.getStopNumber() > 0) { return -1; } else if (o2.getStopNumber() > 0) { return 1; } else { return 0; } } } } }
這個函數(shù)有明顯的先到先得的問題,比如對于compareFlightPrice(a, b)
,如果ab都是非共享非經(jīng)停,那么這個就會把a(bǔ)排到前面,但如果調(diào)用compareFlightPrice(b, a)
,b又會排到前面,所以必須判斷a是非共享非經(jīng)停且b不是非共享非經(jīng)停,才能讓a排在前面。
當(dāng)然除了改比較函數(shù),還有一個解決方式是:給jvm添加啟動參數(shù)。
-Djava.util.Arrays.useLegacyMergeSort=true
還需要注意的是,并不一定你的集合中存在相等的元素,并且比較函數(shù)不符合上面的嚴(yán)謹(jǐn)定義,就一定會穩(wěn)定浮現(xiàn)此異常,實(shí)際上我們在生產(chǎn)環(huán)境出現(xiàn)此異常的概率很小,畢竟java并不會蠢到先去把整個數(shù)組都校驗(yàn)一遍,實(shí)際上它是在排序的過程中發(fā)現(xiàn)你不符合此條件的。所以有可能某種集合順序讓你剛好繞過了此判斷。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- 解決啟動Azkaban報錯問題:java.lang.NoSuchMethodError: com.google.common.collect.ImmutableMap.toImmutableMap
- 詳解java代碼中init method和destroy method的三種使用方式
- Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法
- java中Class.getMethods()和Class.getDeclaredMethods()方法的區(qū)別
- 詳解Java中Method的Invoke方法
- 解決 java.lang.NoSuchMethodError的錯誤
- 解析Java中的Field類和Method類
- java.lang.AbstractMethodError: org.apache.xerces.dom.DocumentImpl.setXmlVersion問題解決方法
- Java反射機(jī)制及Method.invoke詳解
- Java Method類及invoke方法原理解析
相關(guān)文章
springboot vue完成發(fā)送接口請求顯示響應(yīng)頭信息
這篇文章主要為大家介紹了springboot+vue完成發(fā)送接口請求顯示響應(yīng)頭信息,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05SpringBoot+MyBatisPlus對Map中Date格式轉(zhuǎn)換處理的方法詳解
在?SpringBoot?項目中,?如何統(tǒng)一?JSON?格式化中的日期格式。本文將為大家介紹一種方法:利用MyBatisPlus實(shí)現(xiàn)對Map中Date格式轉(zhuǎn)換處理,需要的可以參考一下2022-10-10Java?Chassis3熔斷機(jī)制的改進(jìn)路程技術(shù)解密
這篇文章主要介紹了Java?Chassis?3技術(shù)解密之熔斷機(jī)制的改進(jìn)路程實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01SpringBoot整合mybatis/mybatis-plus實(shí)現(xiàn)數(shù)據(jù)持久化的操作
這篇文章主要介紹了SpringBoot整合mybatis/mybatis-plus實(shí)現(xiàn)數(shù)據(jù)持久化,本節(jié)內(nèi)容我們介紹了數(shù)據(jù)持久化的相關(guān)操作,并且是基礎(chǔ)傳統(tǒng)的關(guān)系型數(shù)據(jù)庫——mysql,需要的朋友可以參考下2022-10-10java實(shí)現(xiàn)遺傳算法實(shí)例分享(打印城市信息)
本文介紹java實(shí)現(xiàn)遺傳算法的實(shí)例,代碼中使用城市名做為數(shù)據(jù),可以打印當(dāng)前代數(shù)的所有城市序列,以及其相關(guān)的參數(shù),大家參考使用吧2014-01-01Java定時任務(wù)實(shí)現(xiàn)優(yōu)惠碼的示例代碼
在Java中實(shí)現(xiàn)定時任務(wù)來發(fā)放優(yōu)惠碼,我們可以使用多種方法,比如使用java.util.Timer類、ScheduledExecutorService接口,或者更高級的框架如Spring的@Scheduled注解,這篇文章主要介紹了Java定時任務(wù)實(shí)現(xiàn)優(yōu)惠碼的實(shí)例,需要的朋友可以參考下2024-07-07