Comparison method violates its general contract!

背景

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

復現

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

Collections.sort(list, new Comparator<Integer>() {
	@Override
	public int compare(Integer o1, Integer o2) {
		return o1 > o2 ? 1 : -1;// 錯誤的方式
	}
});

解決方案

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

修改代碼

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

Collections.sort(list, new Comparator<Integer>() {
	@Override
	public int compare(Integer o1, Integer o2) {
		// return o1 > o2 ? 1 : -1;
		return o1.compareTo(o2);// 正確的方式
	}
});

不修改代碼

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

-Djava.util.Arrays.useLegacyMergeSort=true

這樣就會照舊使用JDK6的排序算法,在不能修改代碼的情況下,解決這個兼容的問題。

分析

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

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

描述的意思是說,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

再回過頭來看我們開篇有問題的實現:

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,以便有心人共同研究,如下:

Integer[] array = 
{0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2, 1, 0, 0, 0, 2, 30, 0, 3};

      (完)

      本文來自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/42012365,轉載請註明。

 

發佈了101 篇原創文章 · 獲贊 1323 · 訪問量 286萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章