為什么重寫 equals
的時候必須重寫 hashCode
大家可能從很多教程中了解到:
SUN官方的文檔中規定"如果重定義equals方法,就必須重定義hashCode方法,以便用戶可以將對象插入到散列(哈希)表中"
那么 SUN 公司是出於什么考慮做了這個規定呢?
在集合框架中的HashSet
,HashTable
和HashMap
都使用哈希表的形式存儲數據,而hashCode
計算出來的哈希碼便是它們的身份證。哈希碼的存在便可以:
- 快速定位對象,提高哈希表集合的性能。
- 只有當哈希表中對象的索引即
hashCode
和對象的屬性即equals
同時相等時,才能夠判斷兩個對象相等。 - 從上面可以看出,哈希碼主要是為哈希表服務的,其實如果不需要使用哈希表,也可以不重寫
hashCode
。但是SUN公司應該是出於對程序擴展性的考慮(萬一以后需要將對象放入哈希表集合中),才會規定重寫equals
的同時需要重寫hashCode
,以避免后續開發不必要的麻煩。
重寫equals
的注意事項
Java語言規范要求equals
需要具有如下的特性:
- 自反性:對於任何非空引用 x,
x.equals()
應該返回true
。 - 對稱性:對於任何引用 x 和 y,當且僅當
y.equals(x)
返回true
,x.equals(y)
也應該返回true
。 - 傳遞性:對於任何引用 x、y 和 z,如果
x.equals(y)
返回true
,y.equals(z)
也應返回同樣的結果。 - 一致性:如果 x 和 y 引用的對象沒有發生變化,反復調用
x.equals(y)
應該返回同樣的結果。 - 對於任意非空引用 x,
x.equals(null)
應該返回false
。
在對象比較時,我們應該如何編寫出一個符合特性的 equals
方法呢,《Core Java》中提出了如下建議:
- 顯式參數命名為 otherObject,稍后將它轉換成另一個叫做 other 的變量。
-
檢測 this 與 otherObject 是否引用同一個對象:
if (this == otherObject) return true;
計算這個等式可以避免一個個比較類中的域,實現優化。
-
檢測 otherObject 是否為 null,如果為 null,返回 false。進行非空校驗是十分重要的。
-
比較 this 與 otherObject 是否屬於同一個類。
- 如果每個子類都重寫了
equals
,使用getClass
檢驗:
if (getClass() != otherObject.getClass()) return false;
- 如果所有子類都使用同一個
equals
,就用instanceof
檢驗:
if (!(otherObject instanceof ClassName)) return false;
- 如果每個子類都重寫了
-
將 otherObject 轉換為相應的類型變量。
ClassName other = (ClassName) otherObject;
-
現在可以對所有需要比較的域進行比較了。
- 基本類型使用
==
比較 - 對象使用
equals
比較 - 數組類型的域可以使用靜態方法
Arrays.equals
檢測相應數組元素是否相等 - 如果所有域匹配,則返回 true
- 基本類型使用
注意:子類重寫父類 equals
方法時,必須完全覆蓋父類方法,不能因為類型錯誤或者其他原因定義了一個完全無關的方法。可以使用 @Override
注解對覆蓋父類的方法進行標記,這樣編譯器便會檢測到覆蓋過程中的錯誤。
重寫 hashCode
的注意事項
散列碼(hash code)是由對象導出的一個整型值。散列碼沒有規律,在不同的對象中通過不同的算法生成,Java中生成 hashCode 的策略為(以下說明均摘自 Java API 8):
-
String 類的 hashCode 根據其字符串內容,使用算法計算后返回哈希碼。
Returns a hash code for this string. The hash code for a String object is computed as s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
-
Integer 類返回的哈希碼為其包含的整數數值。
Returns: a hash code value for this object, equal to the primitive int value represented by this Integer object.
-
Object 類的 hashCode 返回對象的內存地址經過處理后的數值。
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.
在自己的類中想要重寫 hashCode
的話一般怎么做呢?建議合理地組合實例域的散列碼,讓各個不同對象產生的散列碼更加均勻。例如我們現在有一個 Cat
對象,它有 name
、size
和 color
三個不同域,那么可以重寫 hashCode
方法如下:
class Cat { ...... public int hashCode() { //hashCode是可以返回負值的 return 6 * name.hashCode() + 8 * new Double(size).hashCode() + 10 * color.hashCode(); } ...... }
當然還有更好的做法,我們可以直接調用靜態方法 Objects.hash
並提供多個參數。這個方法會對各個參數調用 Object.hashCode
,並組合返回的散列碼。故以上的方法可以縮寫為:
public int hashCode() { return Objects.hash(name, size, color); }
注意: equals
與hashCode
的定義必須一致,兩個對象equals
為true
,就必須有相同的hashCode
。例如:如果定義的equals
比較的是小貓的 name,那么hashCode
就需要散列該 name,而不是小貓的 color 或 size。
參考:
- 《Core Java》卷一
- 【哈希表數據結構】【深入理解hashcode & equals】