hashCode和equals

hashCode和equals是Object的非final方法,它的存在就是用來被重寫的。
Object的equals方法如下:

public boolean equals(Object obj) {
	  return (this == obj);
}

Object的hashCode方法是個native方法。
hashCode返回對象的hash值,主要用戶快速查找。在HashMap,HashTable這類散列數據結構中,都是通過hashCode方法查找對象在散列表中的位置。

什麼情況下需要重寫

equals

Object的equals是用==來判斷兩個對象是否是同一個對象,及對象地址是否相等。而重寫equals爲了達到的目的就是實現對象邏輯相等,即值相等
所以,如果是如下幾種情況,不需要重寫equals方法:

  1. 強調活動實體,而不關心值。比如Thread,我們只在乎是哪個線程,不在乎線程的值,所以只需要比較地址就好。
  2. 不存在邏輯相等。不會用到比較值的功能,所以可以不重寫。
  3. 父類已經重寫了equals,子類只需要用父類重寫的equals方法即可。

hashCode

如果重寫了equals方法,此時必須重寫hashCode方法。
爲什麼?
因爲關於hashCode和equals有這樣的規範:

  1. 如果兩個對象hashCode相等,對象不一定相等(hash衝突的情況);
  2. 如果兩個對象equals方法返回true,hashCode必相等。

如果我們只重寫了equals沒有重寫hashCode,就可能違反上述第2條規範。違反會造成什麼問題呢?在HashMap、HashSet一類散列集合中,會根據對象的hashCode先找對象位置,再根據對象的equals方法判斷是否是同一個值。我們存兩個equals方法返回true的對象到集合,如果我們沒有滿足第2條規範,就會導致集合中存在兩個相同,但是hashCode不同對象,不是散列集合想達到的目的。

怎麼寫

equals重寫準則

  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. 一致性:對於任何非空引用值,如果值沒有被修改,則無論調用多少次equals方法結果始終相等。
  5. 非空性:對於任何非空引用值x,x.equals(null)都應該返回false。

equals重寫技巧

  1. 使用==檢查參數是否爲這個對象的引用:如果是本身,則直接返回,優化性能。
  2. 使用instanceOf檢查參數類型是否正確:如果不是,直接返回false。如果兼容不同類型,很容易違反對稱性。
  3. 將參數類型強制轉換爲正確的類型:第2步保證了該步不會拋出異常。
  4. 對於該類中的“關鍵域”,檢查參數中的域是否與對象中的對應域相等:基本類型的域就用==比較,float域用Float.compare方法,double域用Double.compare方法,至於別的引用域,我們一般遞歸調用它們的equals方法比較,加上判空檢查和對自身引用的檢查,一般會寫成這樣:(field == o.field || (field != null && field.equals(o.field))),而上面的String裏使用的是數組,所以只要把數組中的每一位拿出來比較就可以了。
  5. 編寫完成後思考是否滿足上面提到的對稱性,傳遞性,一致性等等。

hashCode重寫技巧

兩個目標,一個爲不同的目標生成不同的散列值,一個是把實例均勻分佈在所有的散列值上。

引自Effective Java
  1. 把某個非零的常數值,比如17,保存在一個int型的result中;
  2. 對於每個關鍵域f(equals方法中設計到的每個域),作以下操作:
    (1)爲該域計算int類型的散列碼:
    i.如果該域是boolean類型,則計算(f?1:0),
    ii.如果該域是byte,char,short或者int類型,計算(int)f,
    iii.如果是long類型,計算(int)(f^(f>>>32)).
    iv.如果是float類型,計算Float.floatToIntBits(f).
    v.如果是double類型,計算Double.doubleToLongBits(f),然後再計算long型的hash值
    vi.如果是對象引用,則遞歸的調用域的hashCode,如果是更復雜的比較,則需要爲這個域計算一個範式,然後針對範式調用hashCode,如果爲null,返回0
    vii. 如果是一個數組,則把每一個元素當成一個單獨的域來處理。
    (2)result = 31 * result + c。
    ★ 爲什麼採用31result + c,乘法使hash值依賴於域的順序,如果沒有乘法那麼所有順序不同的字符串String對象都會有一樣的hash值,而31是一個奇素數,如果是偶數,並且乘法溢出的話,信息會丟失,31有個很好的特性是31i ==(i<<5)-i,即2的5次方減1,虛擬機會優化乘法操作爲移位操作的。
  3. 返回result
  4. 編寫單元測試驗證有沒有實現所有相等的實例都有相等的散列碼。
發佈了37 篇原創文章 · 獲贊 12 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章