Java中equals和hascode之間的關係

在逛 programcreek 的時候,我發現了一些專一細節但價值連城的主題。好比說:Java 的 equals()hashCode() 是遠房親戚嗎?像這類靈魂拷問的主題,很是值得深刻地研究一下。html

另外,我想要告訴你們的是,研究的過程很是的有趣,就好像在迷宮裏探寶同樣,起初有些不知所措,但通過一番用心的摸索後,不但會找到寶藏,還會有一種茅塞頓開的感受,很是棒。java

對於絕大多數的初級程序員或者說不重視「內功」的老鳥來講,每每停留在「知其然不知其因此然」的層面上——會用,但要說底層的原理,可就只能撓撓頭雙手一攤一張問號臉了。node

很長一段時間內,我,沉默王二也一直處於這種層面上。但我決定改變了,由於「內功」就好像是在打地基,只有把地基打好了,才能蓋起經得住考驗的高樓大廈。藉此機會,我就和你們一塊兒,對「equals() 和 hashCode()」進行一次深刻地研究。程序員

equals()hashCode() 是 Java 的超級祖先類 Object 定義的兩個重要的方法:數組

public boolean equals(Object obj) public int hashCode() 複製代碼

講道理,單從方法的定義上來看,equals()hashCode() 這兩個方法之間沒有任何親戚關係,遠房都夠不上資格。但往深處扒拉,它們之間還真的是有千絲萬縷的關係。究竟是什麼關係呢?若是你們夥比較感興趣的話,就請隨我來,打怪進階嘍!bash

0一、equals()

爲了勾起你們的好奇心,我特地編寫了下面這個示例。ide

public class Cmower {
    private String name;

    public Cmower(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (!(obj instanceof Cmower))
            return false;
        if (obj == this)
            return true;
        return this.name.equals(((Cmower) obj).name);
    }

    public static void main(String[] args) {
        Cmower a1 = new Cmower("沉默王二");
        Cmower a2 = new Cmower("沉默王三");

        Map<Cmower, Integer> m = new HashMap<Cmower, Integer>();
        m.put(a1, 18);
        m.put(a2, 28);
        System.out.println(m.get(new Cmower("沉默王二")));
    }
}
複製代碼

咱們定義了一個 Cmower 類,它有一個字段 name;而後咱們重寫了 equals() 方法:ui

1)若是指定對象爲 null,返回 false;this

2)若是指定對象的類型不是 Cmower 類,返回 false;spa

3)若是指定對象「==」當前對象,返回 true;

4)若是指定對象的 name 和當前對象的 name 相等,返回 true;意味着只要 name 相等,兩個對象就是 equals 的。

在 main 方法中,咱們建立了兩個 Cmower 類型的對象,name 分別爲「沉默王二」和「沉默王三」,並將它們做爲鍵放入了 HashMap 當中;按理說,只要鍵的 name 爲「沉默王二」,程序就應該可以獲取咱們以前放入的 Cmower 對象。但結果卻「出人意料」:

null
複製代碼

可明明 HashMap 中放入了「沉默王二」啊,debug 也能夠證實這一點。

那到底是哪裏出了錯呢?

0二、hashCode()

開門見山地說吧,問題出在 hashCode() 身上,Cmower 類沒有重寫該方法。藉此機會交代一下 equals()hashCode() 這兩個方法之間的關係吧:

1)若是兩個對象須要相等(equals),那麼它們必須有着相同的哈希碼(hashCode);

2)但若是兩個對象有着相同的哈希碼,它們卻不必定相等。

這就好像在說:你和你對象要想在一塊兒天長地久,就必須彼此相愛;但即使你和你對象彼此相愛,但卻不必定真的能在一塊兒。

(扎心了,老鐵)

HashMap 之因此可以更快地經過鍵獲取對應的值,是由於它的鍵位上使用了哈希碼。當咱們須要從 HashMap 中獲取一個值的時候,會先把鍵轉成一個哈希碼,判斷值所在的位置;而後在使用「==」操做符或者 equals() 方法比較鍵位是否相等,從而取出鍵位上的值。

能夠查看一下 HashMap 類的 get() 方法的源碼。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            do {
                if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
複製代碼

在上例中,之因此沒有從 HashMap 中取出 name 爲「沉默王二」的 Cmower 對象,就是由於 put 的時候和 get 的時候兩個對象的哈希碼不一樣的緣由形成的。

明白了緣由以後,咱們就能夠對 Cmower 類進行改造,來看重寫後的 hashCode() 吧。

@Override
public int hashCode() {
    return this.name.hashCode();
}
複製代碼

重寫後的 hashCode() 方法體很是簡單:返回 name 字段的哈希碼。這樣的話,put 和 get 用到的哈希碼就是相同的,由於「沉默王二」的哈希碼是 867758096。再次運行程序,你就會發現輸出結果再也不是 null 而是 18 了。

0三、小結

1)equals() 的做用是用來判斷兩個對象是否相等。

2)hashCode() 的做用是獲取對象的哈希碼;哈希碼通常是一個整數,用來肯定對象在哈希表(好比 HashMap)中的索引位置。

拿 HashMap 來講,它本質上是經過數組實現的。當咱們要獲取某個「值」時,其實是要獲取數組中的某個位置的元素。而數組的位置,就是經過「鍵」來獲取的;更進一步說,是經過「鍵」對應的哈希碼計算獲得的。

3)若是兩個對象須要相等(equals),那麼它們必須有着相同的哈希碼(hashCode);

4)但若是兩個對象有着相同的哈希碼,它們卻不必定相等。

來對照一下官方給出的關於 equals()hashCode() 的解釋:

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

可能有讀者會問:「必定要同時重寫 equals()hashCode() 嗎?」

回答固然是否認的。若是對象做爲鍵在哈希表中,那麼兩個方法都要重寫,由於 put 和 get 的時候須要用到哈希碼和 equals() 方法;

若是對象不在哈希表中,僅用來判斷是否相等,那麼重寫 equals() 就好了。

嘿嘿😜,這下搞清楚 equals()hashCode() 之間的關係了吧!

0四、鳴謝

好了,讀者朋友們,以上就是本文的所有內容了。能看到這裏的都是最優秀的程序員,升職加薪就是你了👍。原創不易,若是以爲有點用的話,請不要吝嗇你手中點讚的權力——由於這將是我寫做的最強動力

相關文章
相關標籤/搜索