1. 程式人生 > >這個坑,你要注意:Comparison method violates its general contract!

這個坑,你要注意:Comparison method violates its general contract!

背景

有部分業務需要進行排序,對比的物件是某實體裡的金額(double 型別),這樣,我們實現了自定義的比較類,結果執行一段時間之後報了錯誤:Comparison method violates its general contract! ,經過校驗,發現錯誤出現在自定義排序上,經網上各種查閱資料發現是jdk7的相容問題,以下將解決過程分享給大家。

錯誤截圖:



重寫的比較方法:

解決方案

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

修改程式碼

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

/**
	 * 對比類:根據持有金額
	 */
	private static class  TenderCollectComparator implements Comparator<TenderCollect> {

		public int compare(TenderCollect b1, TenderCollect b2) {

			return b1.getTocollectmoney().compareTo(b2.getTocollectmoney());
		}
	}


不修改程式碼

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

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  1. -Djava.util.Arrays.useLegacyMergeSort=true  
這樣就會照舊使用JDK6的排序演算法,在不能修改程式碼的情況下,解決這個相容的問題。

分析

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

[plain] view plain copy print?在CODE上檢視程式碼片派生到我的程式碼片
  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?在CODE上檢視程式碼片派生到我的程式碼片
  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?在CODE上檢視程式碼片派生到我的程式碼片
  1. Integer[] array =   
  2. {000000030000000000000000000000,