Comparison method violates its general contract!

背景

16號爲了統一線上服務器運行環境,將兩臺服務器的Tomcat6+JDK6升級到Tomcat7+JDK7,本以爲很簡單的事情,升級後自己驗證也沒問題,沒想到卻悲劇了。升級後,過了半小時運營就找過來反饋問題,部分角色無法登陸系統,由於異常日誌沒有輸出,沒有找到問題,無奈回滾。今天我們就來說說JDK6升級到JDK7會遇到的坑。本文爲了方便搜索,就直接以異常信息作爲文章標題了。

復現

回滾後,到beta環境按照線上的權限配置,復現該問題,加上了error日誌輸出,輸出了文章標題的異常,這個異常是在類似如下代碼中拋出的:

[java] view plain copy
 print?
  1. Collections.sort(list, new Comparator<Integer>() {  
  2.     @Override  
  3.     public int compare(Integer o1, Integer o2) {  
  4.         return o1 > o2 ? 1 : -1;// 錯誤的方式  
  5.     }  
  6. });  

解決方案

先說如何解決,解決方式有兩種。

修改代碼

上面代碼寫的本身就有問題,第4行沒有考慮o1 == o2的情況,再者說我們不需要自己去比較,修改爲如下代碼即可:

[java] view plain copy
 print?
  1. Collections.sort(list, new Comparator<Integer>() {  
  2.     @Override  
  3.     public int compare(Integer o1, Integer o2) {  
  4.         // return o1 > o2 ? 1 : -1;  
  5.         return o1.compareTo(o2);// 正確的方式  
  6.     }  
  7. });  

不修改代碼

那麼問題來了。爲什麼上面代碼在JDK6中運行無問題,而在JDK7中卻會拋異常呢?這是因爲JDK7底層的排序算法換了,如果要繼續使用JDK6的排序算法,可以在JVM的啓動參數中加入如下參數:

[plain] view plain copy
 print?
  1. -Djava.util.Arrays.useLegacyMergeSort=true  
這樣就會照舊使用JDK6的排序算法,在不能修改代碼的情況下,解決這個兼容的問題。

分析

在我以前的認知中,高版本的JDK是可以兼容之前的代碼的,與同事討論了一番另加搜索了一番,事實證明,JDK6到JDK7確實存在兼容問題(不兼容列表)。在不兼容列表中我們可以找到關於Collections.sort的不兼容說明,如下:

[plain] view plain copy
 print?
  1. Area: API: Utilities  
  2. Synopsis: Updated sort behavior for Arrays and Collections may throw an IllegalArgumentException  
  3. Description: The sorting algorithm used by java.util.Arrays.sort and (indirectly) by java.util.Collections.sort has been replaced.   
  4. The new sort implementation may throw an IllegalArgumentException if it detects a Comparable that violates the Comparable contract.   
  5. The previous implementation silently ignored such a situation.  
  6. If the previous behavior is desired, you can use the new system property, java.util.Arrays.useLegacyMergeSort,   
  7. to restore previous mergesort behavior.  
  8. Nature of Incompatibility: behavioral  
  9. RFE: 6804124  

描述的意思是說,java.util.Arrays.sort(java.util.Collections.sort調用的也是此方法)方法中的排序算法在JDK7中已經被替換了。如果違法了比較的約束新的排序算法也許會拋出llegalArgumentException異常。JDK6中的實現則忽略了這種情況。那麼比較的約束是什麼呢?看這裏,大體如下:

  • 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
再回過頭來看我們開篇有問題的實現:
[java] view plain copy
 print?
  1. return x > y ? 1 : -1;  
當x == y時,sgn(compare(x, y))  = -1,-sgn(compare(y, x)) = 1,這違背了sgn(compare(x, y)) == -sgn(compare(y, x))約束,所以在JDK7中拋出了本文標題的異常。

結論

那麼現在是否可以蓋棺定論了,按照上面的分析來看,使用這種比較方式(return x > y ? 1 : -1;),只要集合或數組中有相同的元素,就會拋出本文標題的異常。實則不然,什麼情況下拋出異常,還取決於JDK7底層排序算法的實現,也就是大名鼎鼎的TimSort。後面文章會分析TimSort。本文給出一個會引發該異常的Case,以便有心人共同研究,如下:
[java] view plain copy
 print?
  1. Integer[] array =   
  2. {000000030000000000000000000000,   
  3. 0001100101000010010002100023003};  
      (完)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章