一:怎樣重寫equals()方法?
重寫equals()方法看起來非常簡單,但是有許多改寫的方式會導緻錯誤,并且後果非常嚴重。要想正确改寫equals()方法,你必須要遵守它的通用約定。下面是約定的内容,來自java.lang.Object的規範:
equals方法實作了等價關系(equivalence relation):
1. 自反性:對于任意的引用值x,x.equals(x)一定為true。
2. 對稱性:對于任意的引用值x 和 y,當x.equals(y)傳回true時,
y.equals(x)也一定傳回true。
3. 傳遞性:對于任意的引用值x、y和z,如果x.equals(y)傳回true,
并且y.equals(z)也傳回true,那麼x.equals(z)也一定傳回true。
4. 一緻性:對于任意的引用值x 和 y,如果用于equals比較的對象資訊沒有被修
改,多次調用x.equals(y)要麼一緻地傳回true,要麼一緻地傳回false。
5. 非空性:對于任意的非空引用值x,x.equals(null)一定傳回false。
二:重寫equals方法的要點:
1. 使用==操作符檢查“實參是否為指向對象的一個引用”。
2. 使用instanceof操作符檢查“實參是否為正确的類型”。
3. 把實參轉換到正确的類型。
4. 對于該類中每一個“關鍵”域,檢查實參中的域與目前對象中對應的域值是否匹
配。對于既不是float也不是double類型的基本類型的域,可以使用==操作符
進行比較;對于對象引用類型的域,可以遞歸地調用所引用的對象的equals方法;
對于float類型的域,先使用Float.floatToIntBits轉換成int類型的值,
然後使用==操作符比較int類型的值;對于double類型的域,先使用
Double.doubleToLongBits轉換成long類型的值,然後使用==操作符比較
long類型的值。
5. 當你編寫完成了equals方法之後,應該問自己三個問題:它是否是對稱的、傳
遞的、一緻的?(其他兩個特性通常會自行滿足)如果答案是否定的,那麼請找到
這些特性未能滿足的原因,再修改equals方法的代碼。
三:hashCode
hashCode主要是用于散列集合,通過對象hashCode傳回值來與散列中的對象進行比對,通過hashCode來查找散列中對象的效率為O(1),如果多個對象具有相同的hashCode,那麼散列資料結構在同一個hashCode位置處的元素為一個連結清單,需要通過周遊連結清單中的對象,并調用equals來查找元素。這也是為什麼要求如果對象通過equals比較傳回true,那麼其hashCode也必定一緻的原因。
為對象提供一個高效的hashCode算法是一個很困難的事情。理想的hashCode算法除了達到本文最開始提到的要求之外,還應該是為不同的對象産生不相同的hashCode值,這樣在操作散列的時候就完全可以達到O(1)的查找效率,而不必去周遊連結清單。假設散列中的所有元素的hashCode值都相同,那麼在散列中查找一個元素的效率就變成了O(N),這同連結清單沒有了任何的差別。
hashCode()的傳回值和equals()的關系如下:
- 如果x.equals(y)傳回“true”,那麼x和y的hashCode()必須相等。
- 如果x.equals(y)傳回“false”,那麼x和y的hashCode()有可能相等,也有可能不等。
四. 設計 hashCode() [1] 把某個非零常數值,例如 17 ,儲存在 int 變量 result 中; [2] 對于對象中每一個關鍵域 f (指 equals 方法中考慮的每一個域): [2.1]boolean 型,計算 (f ? 0 : 1); [2.2]byte,char,short 型,計算 (int); [2.3]long 型,計算 (int) (f ^ (f>>>32)); [2.4]float 型,計算 Float.floatToIntBits( afloat ) ; [2.5]double 型,計算 Double.doubleToLongBits( adouble ) 得到一個 long ,再執行 [2.3]; [2.6] 對象引用,遞歸調用它的 hashCode 方法 ; [2.7] 數組域,對其中每個元素調用它的 hashCode 方法。 [3] 将上面計算得到的散列碼儲存到 int 變量 c ,然後執行 result=31*result+c; [4] 傳回 result 。 這個算法存在這麼幾個問題需要探讨: 1. 為什麼初始值要使用非0的整數?這個的目的主要是為了減少hash沖突,考慮這麼個場景,如果初始值為0,并且計算hash值的前幾個域hash值計算都為0,那麼這幾個域就會被忽略掉,但是初始值不為0,這些域就不會被忽略掉,示例代碼:
| |
|
| |
|
| |
|
| |
|
| |
| |
| |
|
| |
| |
| |
| |
| |
| |
| |
| |
|
| |
| |
| |
| |
| |
| |
|
| |
如果hashCode中result的初始值為0,那麼對象t和對象t2的hashCode值都會為0,盡管這兩個對象不同。但如果result的值為17,那麼計算hashCode的時候就不會忽略這些為0的值,最後的結果t1是15699857,t2是506447 2. 為什麼每次需要使用乘法去操作result? 主要是為了使散列值依賴于域的順序,還是上面的那個例子,Test t = new Test(1, 0)跟Test t2 = new Test(0, 1), t和t2的最終hashCode傳回值是不一樣的。 3. 為什麼是31? 31是個神奇的數字,因為任何數n * 31就可以被JVM優化為 (n << 5) -n,移位和減法的操作效率要比乘法的操作效率高的多。