hashCode和equals方法:自定義HashMap的key時需要注意什麼?

使用

當我們需要自定義HashMap這種散列數據結構(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key時候:
需要重寫hashCode()和equals(Object o)方法

使用方法

public class Key {
    private Integer id;
    public Integer getId(){
        return id;
    }
    public Key(Integer id){
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
       if(o == null || !(o instanceof  Key)){
           return false;
       }else {
           return this.getId().equals(((Key)o).getId());
       }
    }

    @Override
    public int hashCode() {
       int hashCode =  id.hashCode();
       System.out.println(hashCode);
        return hashCode;
    }
}
    public static void main(String[] args) {
        Key k1 = new Key(1);
        Key k2 = new Key(1);

        HashMap<Key,String> hm= new HashMap<Key,String>();
        hm.put(k1,"key with id is 1");

        System.out.println(hm.get(k2));
    }

我們想通過hm.get(k2)來獲取得到“key with id is 1”的打印。

爲什麼

1.HashMap爲了查詢效率,存放的時候是通過散列表來存儲的。所以需要重寫hashCode來保證存儲的數組下標一致。
2.equals方法是用來保證相等的。

通過散列來存儲,何爲散列表?

散列思想

正常我們查詢是O(n)。
例如:
在長度爲n(假設是500)的線性表(Arraylist)存放着無序的數字。
如果我們要找到一個指定的數字,就不得不通過從頭到位一次遍歷來查找,這樣的平均查找次數是n除以2(250)

散列表用的是數組支持按照下標隨機訪問數據的特性,所以散列表其實就是數組的一種擴展,由數組演化而來。可以說,如果沒有數組,就沒有散列表。

利用數組支持根據下標隨機訪問的時候,時間複雜度是O(1)。
例如我們生活中的一個場景:
學校需要點名,學校3年2班的學號25定義爲:030225,這個人是張三。
如果我們想要找到張三,我們只需大喊一聲030225,則張三就會喊一聲到!
我們很快就可以找到張三這個人。
030225的編號我們叫作鍵(key)或者關鍵字
張三我們稱作爲散列值
030225轉化爲數組下標的映射方法就叫作散列函數
散列函數在散列表中起着非常關鍵的作用。正常情況下散列函數的計算是比較複雜的。

我們看稍微複雜一點:
存放元素和存放位置是用hash來關聯的。hash函數是x*x%5
在這裏插入圖片描述
6這個元素會放到索引爲1的位置。7的hash函數結果爲4。
我們想要從散列表中找6這個元素。我們可以先對元素進行一次hash運算。然後再從結果中找到這個位置。

如果再放入8的話,就會產生Hash衝突。
在這裏插入圖片描述
hash衝突通過一個鏈來將7和8關聯起來。這個方法就是鏈地址法。

java中Object類會有HashCode方法。這個HashCode方法就是我們所說的散列函數。HashCode返回的值就是散列值。

我們再回頭看上面的結論:
1.HashMap爲了查詢效率,存放的時候是通過散列表來存儲的。所以需要重寫hashCode來保證存儲的數組下標一致。
就明白了。

equals方法是用來保證相等的

Object.equals方法只是比較的對象的地址。

再次看一開始的例子:
```java
public class Key {
    private Integer id;
    public Integer getId(){
        return id;
    }
    public Key(Integer id){
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
       if(o == null || !(o instanceof  Key)){
           return false;
       }else {
           return this.getId().equals(((Key)o).getId());
       }
    }

    @Override
    public int hashCode() {
       int hashCode =  id.hashCode();
        return hashCode;
    }
}
    public static void main(String[] args) {
        Key k1 = new Key(1);
        Key k2 = new Key(1);

        HashMap<Key,String> hm= new HashMap<Key,String>();
        hm.put(k1,"key with id is 1");

        System.out.println(hm.get(k2));
    }

沒有重寫hahcode方法的時候,此調用是不對的調用結果是null。
原因:
當我們向hashcode裏面放k1的時候,首先會調用Key這個類的HashCode方法來計算它的Hash值,隨後把這個值放入這個值所索引的內存的位置。

沒有重新定義HashCode的方法,它就會調用Object類的Hashcode方法。而Object裏面返回的Hash值,是Key1的內存地址。

k2也是如此,但是k1和k2的內存地址是不一樣的。

如果我們用k2去拿

hm.get(k2)

自然拿不到k1的值。

如果重寫了hash值,則調用的hashCode是一樣的。但是比較兩個數是否一樣,光hashCode值一樣是不行的。並不保證k1和k2真正的相等。還需要重寫equals方法。因爲hashcode相等的時候,我們僅僅保證hashmap的數組下標相等。hash過程中會有散列衝突。真正保證相等的是equals方法。

如果不重寫equals方法,它會調用equals方法的內存地址是否是一樣。由於k1和k2是new出來的。他們的內存地址是絕對不一樣的。

如何重寫?

重寫hashcode方法時候,我們判斷定義可以就是如果兩個對象一樣,他們的id就是一樣的。
equals不爲空或者是Object纔來判斷內存地址。

    @Override
    public boolean equals(Object o) {
       if(o == null || !(o instanceof  Key)){
           return false;
       }else {
           return this.getId().equals(((Key)o).getId());
       }
    }

    @Override
    public int hashCode() {
       int hashCode =  id.hashCode();
        return hashCode;
    }

再次總結:

我們終於知道了這個使用:

當我們需要自定義HashMap這種散列數據結構(HashSet,HashMap,LinkedHashMap,LinkedHashSet)的Key時候:
需要重寫hashCode()和equals(Object o)方法

參考:

《Java編程思想》 17.9散列與散列碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章