關於如何改變
Java類中的equals與hashCode方法,Effective Java一書中提供了一個行之有效方法。改寫equals方法的步驟為:
- 使
用==操作符檢查“實參是否為指向對象的一個引用”。
- 使用instanceof操作符檢查“實參是否為正確的類型”。
- 把
實參轉換到正確的類型。
- 對於該類中每一個“關鍵”域,檢查實參中的域與當前對象中對應的域值是否匹配。
其中最後一步書中的描述為:
- 對於既不是float也不是double類型的簡單類型域,可以使用==操作符進行比較;
- 對
於對象參考網域,可以遞迴地調用equals方法;
- 對於float域,先使用Float.floatToIntBits轉換成int類型
的值,然後使用==操作符比較int類型的值;
- 對於double域,先使用Double.doubleToLongBits轉換成
long類型的值,然後使用==操作符比較long類型的值;
- 對於數組域,把以上這些指導原則應用到每個元素上。
有些對象參考網域包含null是合法的,所以為了避免可能導致NullPointerException
異常,使用下面的習慣用法來比較這樣的域:
(field == o.field || (field != null && field.equals(o.field))
改寫equals時總是要改寫hashCode,改寫hashCode方法的步驟為:
- 把某個非零常數值比如說17,
儲存在一個叫result的int類型的變數中。
- 對於對象中每一個關鍵域f(指equals方法中考慮的每一個域),完成以下步驟:
- 為該域計算int類型的散列碼c:
- 如果該域是boolean類型,則計算(f ? 0 : 1)。
- 如果該域是byte、
char、short或者int類型,則計算(int)f。
- 如果該域是long類型,則計算(int)(f ^ (f
>>> 32))。
- 如果該域是float類型,則計算Float.floatToIntBits(f)。
- 如
果該域是double類型,則計算Double.doubleToLongBits(f)得到一個long類型的值,然後按照步驟2.1.3,對該
long型值計算散列值。
- 如果該域是一個對象引用,並且該類的equals方法通過遞迴調用equals的方式來比較這個域,則同樣對
這個域遞迴調用hashCode。如果這個域的值為null,則返回0。
- 如果該域是一個數組,則把每一個元素當做單獨的域來處理。
- 按照下面的公式,把步驟a中計算得到的散列碼c組合到result中:result = 37 * result + c
- 返回result。
上述方案中關於“關鍵”域的計算過程是比較複雜的,看一看下面這個簡單類Point實現的equals與
hashCode方法:
public class Point {<br /> private final int x;<br /> private final int y;<br /> public Point(int x, int y) {<br /> this.x = x;<br /> this.y = y;<br /> }<br /> public int getX() {<br /> return x;<br /> }<br /> public int getY() {<br /> return y;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> // step 1<br /> if (other == this) {<br /> return true;<br /> }<br /> // step 2<br /> if (!(other instanceof Point)) {<br /> return false;<br /> }</p><p> // step 3<br /> Point that = (Point) other;<br /> // step 4<br /> boolean result =<br /> (this.getX() == that.getX()) &&<br /> (this.getY() == that.getY());<br /> return result;<br /> }<br /> @Override<br /> public int hashCode() {<br /> // step 1<br /> int result = 17;<br /> // step 2<br /> result = 37 * result + getX();<br /> result = 37 * result + getY();<br /> // step 3<br /> return result;<br /> }<br />}如果“關鍵”域數量較多,實現上難免出現錯誤。Commons
Lang庫為我們提供了兩個工具類用於簡化equals方法的第4步及hashCode方法的第2步,它們分別為EqualsBuilder與HashCodeBuilder類。如上例的Commons Lang庫版本:import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class Point {<br /> private final int x;<br /> private final int y;<br /> public Point(int x, int y) {<br /> this.x = x;<br /> this.y = y;<br /> }<br /> public int getX() {<br /> return x;<br /> }<br /> public int getY() {<br /> return y;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> // step 1<br /> if (other == this) {<br /> return true;<br /> }<br /> // step 2<br /> if (!(other instanceof Point)) {<br /> return false;<br /> }</p><p> // step 3<br /> Point that = (Point) other;<br /> // step 4<br /> return new EqualsBuilder()<br /> .append(this.getX(), that.getX())<br /> .append(this.getY(), that.getY())<br /> .isEquals();<br /> }<br /> @Override<br /> public int hashCode() {<br /> return new HashCodeBuilder(17, 37)<br /> .append(this.getX())<br /> .append(this.getY())<br /> .toHashCode();<br /> }<br />}