昨天提到了覆寫equals和覆寫hashCode需要注意的事情,今天就來談談,覆寫hashCode可以遵照哪些規則覆寫。
沒有在equals
用來判斷物件相不相等的欄位,最好在hashCode
裡面也不要拿來計算hash code。
一開始可以先指定一個是型別是int
的非0值(例如17),作為初始值存在result
裡面。
書上說沒加初始值會增加hash碰撞,但我在想範例的時候測了幾次發現,有加沒加初始值似乎都會遇到一樣的碰撞問題,反而是把hash值差異比較大的欄位先計算,比較能夠避免碰撞。
以下面的範例為例,一開始先計算型別為boolean
的isChild
,結果在name
一樣的情況下,age
一個10一個41會發生碰撞。
class PersonForHash {
private boolean isChild;
private String name;
private int age;
public PersonForHash(boolean isChild, String name, int age) {
this.isChild = isChild;
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (isChild ? 1 : 0);
result = 31 * result + age;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
public static void main(String[] args) {
PersonForHash p1 = new PersonForHash(true, "Lucky", 10);
PersonForHash p2 = new PersonForHash(false, "Lucky", 41);
System.out.println("Hash code of p1: " + p1.hashCode()); //74279438
System.out.println("Hash code of p2: " + p2.hashCode()); //74279438
}
但如果上面的例子先計算age
,再計算isChild
,p1
的hash code會是74287808,p2
是74317568,會有些許的差距。
對於類別中的欄位,可以根據不同型別,用以下方式計算hash code。
boolean
,true
的hash code是1,false
是0 => (f ? 1 : 0)
byte
、char
、short
或int
,強制轉換成int
後,即是hash code => (int) f
long
,先右移32 bit之後再XOR自己,最後轉成int
=> (int)(f^(f>>>32))
float
,可以用Float.floatToIntBits
幫忙轉換=> Float.floatToIntBits(f)
double
,先轉成long
,再做一次型別是long
的轉換步驟 => long bits = Double.doubleToLongBits(f)
=> (int) (bits ^ (bits >>> 32))
hashCode
,但如果該欄位的結果是null
,直接用0當hash codeArrays.hashCode
計算整個array的hash code。最後,用下面的公式去計算整個類別的hash code,c
代表每個欄位的hash code。而為什麼是用31去乘以result呢?因為31是質數,無法由其他數字組成,所以hash code與31相乘之後,發生碰撞的機率較低,另外,31 * result
電腦的處理器可以翻譯成(i << 5) - i
去運算,對於處理器來說,與31相乘的performance會比其他數字更好。
result = 31 * result + c
equals
是true
的物件,hash code有沒有相同。關於覆寫equals和覆寫hashCode的部分,這幾天大概都說得差不多了~