重寫equals和hashCode的原則規範
當符合以下條件時不需要重寫equals方法:
1. 一個類的每一個例項本質上都是唯一的。
2. 不關心一個類是否提供了“邏輯相等”的測試功能
3. 超類已經改寫了equals方法,並且從超類繼承過來的行為對於子類也是合適的。
4. 一個類時私有的或者是package私有的,並且可以確定它的equals方法永遠不會被呼叫。(這種情況下最好將equals方法改寫成以下方式:
public boolean equals(Object obj){
throws new UnsupportOperationException();
}
只有當一個類有自己特定的“邏輯相等”概念,而且超類也沒有改寫equals以實現期望的行為,我們需要改寫equals方法。通常適用於“值類”。
在改寫equals方法時,也要遵守他們的通用約定(equals方法實現了等價關係):
1. 自反性:x.equals(x) = true;
2. 對稱性:如果有x.equals(y) = true,那麼一定有y.equals(x) = true;
3. 傳遞性:對任意的x,y,z。如果有x.equals(y) = y.equals(z) = true,那麼一定有x.equals(z)= true;
4. 一致性:無論多少次呼叫,x.equals(y)總會返回相同的結果。
5. 非空性(暫定):所有的物件都必須!=null;
上面的只是理論性的說法,更加具體的做法如下:
1. 使用==操作符檢查“實參是否為指向物件的一個引用”,如果是則返回true;
2. 使用instanceof操作符檢查“實參是否為正確的型別”,如果不是,則返回false;
3. 將實參裝換為正確的型別;
4. 對於該類中的每一個關鍵域,檢查實參中的域與當前物件中對應的域是否匹配。如果所有測試都成功,則返回true,否則返回false。
5. 方法完成之後,確定equals方法的對稱性,傳遞性,一致性。
一些忠告:
1.改寫equals方法的時候,必須改寫hashCode方法;
2.不要把equals宣告中的Object物件替換為其他型別;
改寫的形式必須為:public boolean equals(Object obj){...code segment...}
改寫equals時總要改寫hashCode
hashCode的通用約定如下:
1. 只要物件equals方法涉及到的關鍵域內容不改變,那麼這個物件的hashCode總是返回相同的整數。(如果關鍵域內容改變,則hashCode返回的整數就可以改變)。
2. 如果兩個物件的equals(Object obj)方法時相等的,那麼呼叫這兩個物件中的任意一個物件的hashCode方法必須產生相同的整數結果。如果兩個物件equals方法不同,那麼必定返回不同的hashCode整數結果。(簡而言之:相等的物件必須有相等的雜湊碼即hashCode);
一個好的hashCode方法趨向於“為不相等的物件產生不相等的雜湊碼”理想情況下的雜湊函式應該把一個集合中不相等的例項均勻分佈到所有可能的雜湊值上。下面給出一種參考方法:
1. 把某個非零常數值儲存在一個叫做result的int型別的變數中
2. 為該物件中的每一個關鍵域f計算int型別的雜湊碼。
a) 為該域計算int型別的雜湊碼c:
i.如果域是Boolean型別,計算:(f?0:1)
ii.如果是byte,char,short,int型別,計算:(int)f
iii.如果是long型別,計算:(int)(f^(f>>32))
iv.如果是float型別,計算:Float.floatToIntBits(f)
v.如果是double型別,計算Double.doubleToLongBits(f)得到long型別的值,在按照long值對待,繼續進一步計算
vi.如果是物件引用,遞迴呼叫hashCode方法計算,如果遇到為null的關鍵域,則返回0
vii.如果是陣列,將每一個元素都當做單獨的域來計算,遞迴應用上述規則
b) 按照下面公式,將a得到的雜湊碼c組合到result中
result = 37*result + c;
3. 返回result
4. 寫完之後,檢查hashCode方法是否相等的例項具有相等的雜湊碼,並找出錯誤原因。