昨天提到了覆寫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的部分,這幾天大概都說得差不多了~