1. 程式人生 > 其它 >重寫equals的同時為啥要重寫hashCode?

重寫equals的同時為啥要重寫hashCode?

技術標籤:javajavahashcode程式語言

HashMap引申的問題

HashMap在重寫equal一定要重寫hashCode

Effective java這本書中,存在這麼一句話,**在每個覆蓋了equals方法的類中,都必須覆蓋hashCode方法。**大部分人應該都知道這個規則,但為什麼要這樣做,以及不這麼做會出現這麼問題呢?接下來,將對以上問題做探討。

下面有一個重寫了equals方法的PhoneNum類:

class PhoneNum{
    private final int a,b,c;

    PhoneNum(int a, int b, int c) {
        this
.a = a; this.b = b; this.c = c; } public boolean equals(Object o){ if(o==this){ return true; } if(! (o instanceof PhoneNum) ){ return false; } PhoneNum pn=(PhoneNum) o; return pn.a==a&&pn.b==b&&
pn.c==c; } }

注意:此類並沒有重寫hashCode方法。

接下來,我把這個類的例項put進hashMap

class Test{
    static HashMap hashMap=new HashMap();

    public static void main(String[] args) {

        hashMap.put(new PhoneNum(1,1,1),"obj1");

        Object value=hashMap.get(new PhoneNum(1,1,1)); //獲取值
        System.
out.println(value); } }

猜一猜,程式會輸出什麼?為obj1嗎?

下面來看看輸出結果:

在這裡插入圖片描述

接下來我再在PhoneNum類中重寫hashCode方法:

class PhoneNum{
    private final int a,b,c;

    PhoneNum(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public boolean equals(Object o){
        if(o==this){
            return true;
        }
        if(! (o instanceof PhoneNum) ){
            return false;
        }
        PhoneNum pn=(PhoneNum) o;
        return pn.a==a&&pn.b==b&&pn.c==c;
    }
    public int hashCode(){
        return 42;
    }

}

再執行Test中main方法,得到的輸出:

在這裡插入圖片描述

是不是想問一句為啥呢?hashCode有這麼強大嗎

首先,我們先看看hashCode到底是啥,有啥作用

我們先找找Object中的hashCode方法,是不是想問為啥要在Object中找,記住,Java中的所有類都繼承自Object,是不是又要問沒有用extends關鍵字,是怎麼繼承的,這個以後再說,先記住這點。

public native int hashCode();

oops!!原來是本地方法,而且實現體不是用java實現的

再來看看官方文件對hashCode的描述:

hashCode方法返回該物件的雜湊碼值。支援該方法是為雜湊表提供一些優點,例如,java.util.Hashtable 提供的雜湊表。 

hashCode 的常規協定是: 
在 Java 應用程式執行期間,在同一物件上多次呼叫 hashCode 方法時,必須一致地返回相同的整數,前提是物件上 equals 比較中所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。 
如果根據 equals(Object) 方法,兩個物件是相等的,那麼在兩個物件中的每個物件上呼叫 hashCode 方法都必須生成相同的整數結果。 
以下情況不 是必需的:如果根據 equals(java.lang.Object) 方法,兩個物件不相等,那麼在兩個物件中的任一物件上呼叫 hashCode 方法必定會生成不同的整數結果。但是,程式設計師應該知道,為不相等的物件生成不同整數結果可以提高雜湊表的效能。 
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的物件返回不同的整數。(這一般是通過將該物件的內部地址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實現技巧。) 

當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定宣告相等物件必須具有相等的雜湊碼。

那麼,物件的hashCode到底是怎麼來的呢

通過物件的內部地址(也就是實體地址)轉換成一個整數,然後該整數通過hash函式的演算法就得到了hashCode。那麼hashCode有什麼用呢?HashCode是用來在雜湊儲存結構中確定物件的儲存地址的。換句話說,hashCode是用來物件定位其在hash表中位置的。HashCode的作用主要是為了查詢的快捷性。比如,在查詢物件時,根據物件的hashCode就快速定位物件所在hash表的位置,但此時要注意一點,也就是hash衝突,hash衝突也就是不同物件的hashCode一樣,這時候在hash表中存放的位置也一樣,這時候就可以用連結串列的形式把這些hashCode相同的物件串起來,這也就是解決hash衝突最常用的方法——拉鍊法,在根據hashCode查到具體位置後,將要查詢的物件與對應位置的所有物件做比對,直到查到物件。

這下明白了hashCode的作用,那麼hashCode與equals又有什麼關係呢,先來看看equals方法吧

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

equals方法採用的是地址比較,至少說是跟hashCode毫無關係。

但是為什麼官方文件會強調重寫了equals,就一定要重寫hashCode,我們先回憶一下,HashMap,HashSet以及HashTable,它們在新增元素的時候,是根據什麼來確定存放位置的。這裡以HashMap為例子,HashMap在確定元素在Node陣列中的位置時,在hash函式中,用hashCode的高16位與低16位做了異或運算。可見,HashMap在確定位置時用到了hashCode。

現在,我們再回到之前的程式碼,hashMap.put(new PhoneNum(1,1,1),“obj1”); 我put進一個key為PhoneNum物件**,Object value=hashMap.get(new PhoneNum(1,1,1));** 現在我又想獲取值。其實在這裡我new了兩個例項,hashCode是不一樣的,HashMap又是根據hashCode定位雜湊桶的位置,這時候,就會出現存的在這個桶,但是在另一個桶找,肯定是找不到的,HashMap就會返回null;那如果存跟找正好在一個桶裡呢,HashMap也會優先比對hashCode,如果hashCode不匹配,也就不會去驗證物件的等同行,直接返回null。

所以,會有重寫equals方法一定要重寫hashCode的規則。