1. 程式人生 > >hashCode和equals方法的重寫

hashCode和equals方法的重寫

我們都知道,在JAVA世界中,萬物皆物件。而equals和hashCode這兩個方法也在Object類裡被定義,先來看這兩個方法在Object裡面是如何實現的:

先看equals方法

public boolean equals(Object obj) {
        return (this == obj);
    }
equals方法實現很簡單,就是將某物件拿來和原始物件進行對比,如果它倆指向同一個物件,那麼返回true,否則返回false;


再來看hashCode方法:
public native int hashCode();

這個更簡單,竟然只有一個方法定義,連方法實現都沒有。注意其中的關鍵字native, 從網上查了一下才知道,一個Native Method方法就是一個JAVA呼叫非JAVA程式碼的介面,native這個方法實現在C++中,也就是說,這個方法的實現不是由JAVA來實現的,僅僅是告知JAVA去呼叫C++裡的一個函式。原始碼都在C++裡邊,我們不需要去理會!

 

看完這兩個我們再來看String 是怎樣重寫這兩個方法的:

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

首先,先來驗證anObject 所代表的物件是當前物件嗎? 是同一個返回true 結束判斷; 否則返回false,然後進入下一步判斷,anObject 是否是當前類的一個例項,即對比物件所屬同一個類才有比較的意義,貓和貓比較,狗和狗比較,反之,老鼠和向日葵有啥好比較的呢,都不是一個物種對吧。然後兩者如果所屬同一個類,char[]陣列用於儲存String 的每一個字元,先進行字元長度對比,再逐個去比較每一個字元,當每一個字元都相同 才代表這兩個物件是同一個!!

 

為啥要重寫equals呢???

String  str1 = new String("apple");
String  str2 = new String("apple");
System.out.println(str1==str2);             // false
System.out.println(str1.equals(str2));      // true

用 == 返回 false , == 是用於判斷兩個物件是否是同一個,而原本Object類裡的 equals 方法也是比較兩者是否是同一個物件,那如何我們不關心這兩個是否是同一個物件,只想知道他們的 值 是不是一樣該怎麼辦? String 類重寫了equals 方法就是幹這個事的, 比較的是物件的"值"。

 

接著看String 類的hashCode 方法:

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

簡單講,就是把String 字串先轉換成 字元陣列,遍歷每一個字元,最終返回一個 int 型別的雜湊值。

 

hashCode的存在 ???

  先來了解一下為何會有 hashCode 的存在,  這《JAVA程式設計思想》一書中 P495頁有這麼一段話:

“設計hashCode()時最重要的因素就是:無論何時,對同一個物件呼叫hashCode()都應該產生同樣的值。如果在講一個物件用put()新增進HashMap時產生一個hashCdoe值,而用get()取出時卻產生了另一個hashCode值,那麼就無法獲取該物件了。所以如果你的hashCode方法依賴於物件中易變的資料,使用者就要當心了,因為此資料發生變化時,hashCode()方法就會生成一個不同的雜湊碼”。

這段定義在講啥呢,咋這麼亂呢?? 我們可以簡單理解成: 為每一個物件都分配一個編號,編號是根據物件的關鍵特性而決定的,假如物件是人,那麼編號就可以根據人的姓名和年齡兩個因素一起來決定,畢竟找到兩個年齡姓名都相同的不同人還是挺少見的吧。  

那麼經過equals比較為true的兩個物件肯定有著同樣的hashCode(相同的編號);

反之,hashCode相同是否能確定這兩個物件equals返回為true呢?? 測試一下:

String s1 = "ac";
String s2 = "bD";
System.out.println("s1的hashCode是: " + s1.hashCode());
System.out.println("s2的hashCode是:" + s2.hashCode());

 輸出結果:兩個都為 3106;

從這裡可以確定的是,hashCode相同,但顯然這兩個String 不是同一個 ,equals 比較也是 false 的。

 

hashCode 的優點:

以上說了hashCode初始建立的意義,即給每個物件都編號一個小編號。那我們為啥要編號!!!不編號行不行!!

其實呢,編號的目的是為了簡化一些操作,感慨下編寫語言的大師們想的都太周到了。學過JAVA的同學都知道HashMap這麼一個容器,適用於鍵值對儲存(key-value)。

假設有一個HashMap容器: key:儲存丈夫; value:妻子。我們根據key 值來找到這個男人的妻子,key不能存放相同的值。

當我們向這個容器填充 ("李四",“李四的妻子”)時,發現這個容器已經有10000條資料了,我們如何判斷這個容器裡有沒有"李四"這個key呢?? 一條條記錄慢慢查詢嗎?? 李四這個物件裡萬一有幾十個欄位,查詢太慢了,hashCode就是為了解決這個問題的,為李四生成一個hashCode 編號,儲存之前直接查當前容器是否有這麼一個hashCode 編號, 沒有就把這條記錄插進去,有就直接覆蓋之前的資料,使得速度大提高。

同樣,想查HashMap中是否存在某一條記錄,不需要挨個遍歷每條資料,那樣太慢了!!! 直接找這個key的 hashCode是否存在於HashMap中, 不存在直接返回false, 存在則繼續進行equals的比較。

 

把Effective書中的話搬過來了,對比前面的測試應該好理解了:

  • 在程式執行期間,只要equals方法的比較操作用到的資訊沒有被修改,那麼對這同一個物件呼叫多次,hashCode方法必須始終如一地返回同一個整數。
  • 如果兩個物件根據equals方法比較是相等的,那麼呼叫兩個物件的hashCode方法必須返回相同的整數結果。
  • 如果兩個物件根據equals方法比較是不等的,則hashCode方法不一定得返回不同的整數。

 

最後總結:

1) 只要重寫 equals,就必須重寫 hashCode。

2) 因為 Set 儲存的是不重複的物件,依據 hashCode 和 equals 進行判斷,所以 Set 儲存的 物件必須重寫這兩個方法。

3) 如果自定義物件做為 Map 的鍵,那麼必須重寫 hashCode 和 equals。