== 和 equals.方法 hashCode方法以及他們與HashMap相關


一,==和equalsjava

解釋一下==號,它比較的是一個對象在內存中的地址值,node

一、若是兩個對象相同,那麼它們的hashCode值必定要相同;算法

二、若是兩個對象的hashCode相同,它們並不必定相同(上面說的對象相同指的是用eqauls方法比較。) app


值類型是存儲在內存中的堆棧(之後簡稱棧),而引用類型的變量在棧中僅僅是存儲引用類型變量的地址,而其自己則存儲在堆中。
函數

       ==操做比較的是兩個變量的值是否相等,對於引用型變量表示的是兩個變量在堆中存儲的地址是否相同,即棧中的內容是否相同。this

       equals操做表示的兩個變量是不是對同一個對象的引用,即堆中的內容是否相同。(Obejct類使用的是==來實現equals,見下文)編碼

      ==比較的是2個對象的地址,而equals比較的是2個對象的內容。spa

      顯然,當equals爲true時,==不必定爲true;設計

好比2個字符串對象
code

String s1 = new String("str");

String s2 = new String("str");

若是用==號比較,會返回false,由於建立了兩個對象,他們在內存中地址的位置是不同的。


equals的狀況比較複雜,它是java.lang.Object類中的一個方法。由於java中全部的類都默認繼承於Object,因此全部的類都有這個方法。


在Object類源碼中是這樣寫的。

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

他一樣使用==號進行內存地址的比較。可是許多java類中都重寫了這個方法,好比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;
}


String裏的方法,若是==號比較不相等,還會進行一下值的比較。

因此equals方法具體的做用要看當前的那個類是如何實現重寫父類中該方法的。若是沒有重寫該方法,那麼他和==號等價。



二,hashCode和equals

總的來講,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內的元素是有序的,元素能夠重複;後者元素無序,但元素不可重複

首先,想要明白hashCode的做用,必需要先知道Java中的集合。  

總的來講,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。 前者集合內的元素是有序的,元素能夠重複;後者元素無序,但元素不可重複。

那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢? 這就是Object.equals方法了。可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。 也就是說,若是集合中如今已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大下降效率。  

因而,Java採用了哈希表的原理。哈希(Hash)其實是我的名,因爲他提出一哈希算法的概念,因此就以他的名字命名了。 哈希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上。初學者能夠這樣理解,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能並非)。   

這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一會兒能定位到它應該放置的物理位置上。 若是這個位置上沒有元素,它就能夠直接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。 因此這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大下降了,幾乎只須要一兩次。 

因此,Java對於eqauls方法和hashCode方法是這樣規定的:

一、若是兩個對象相同,那麼它們的hashCode值必定要相同;

二、若是兩個對象的hashCode相同,它們並不必定相同(上面說的對象相同指的是用eqauls方法比較。)  

你固然能夠不按要求去作了,但你會發現,相同的對象能夠出如今Set集合中。同時,增長新元素的效率會大大降低。

hashcode這個方法是用來鑑定2個對象是否相等的。 那你會說,不是還有equals這個方法嗎? 不錯,這2個方法都是用來判斷2個對象是否相等的。可是他們是有區別的。 通常來說,equals這個方法是給用戶調用的,若是你想判斷2個對象是否相等,你能夠重寫equals方法,而後在代碼中調用,就能夠判斷他們是否相等 了。簡單來說,equals方法主要是用來判斷從表面上看或者從內容上看,2個對象是否是相等。

舉個例子,有個學生類,屬性只有姓名和性別,那麼咱們能夠 認爲只要姓名和性別相等,那麼就說這2個對象是相等的。 hashcode方法通常用戶不會去調用,好比在hashmap中,因爲key是不能夠重複的,他在判斷key是否是重複的時候就判斷了hashcode 這個方法,並且也用到了equals方法。這裏不能夠重複是說equals和hashcode只要有一個不等就能夠了!因此簡單來說,hashcode相 當因而一個對象的編碼,就好像文件中的md5,他和equals不一樣就在於他返回的是int型的,比較起來不直觀。咱們通常在覆蓋equals的同時也要 覆蓋hashcode,讓他們的邏輯一致。舉個例子,仍是剛剛的例子,若是姓名和性別相等就算2個對象相等的話,那麼hashcode的方法也要返回姓名 的hashcode值加上性別的hashcode值,這樣從邏輯上,他們就一致了。 要從物理上判斷2個對象是否相等,用==就能夠了。

在Java語言中,equals()和hashCode()兩個函數的使用是緊密配合的,你要是本身設計其中一個,就要設計另一個。在多數狀況 下,這兩個函數是不用從新設計的,直接使用它們的默認設計就能夠了。可是在一些狀況下,這兩個函數最好是本身設計,才能確保整個程序的正常運行。最多見的是當 一個對象被加入收集對象(collection object)時,這兩個函數必須本身設計。更細化的定義是:若是你想將一個對象A放入另外一個收集對象B裏,或者使用這個對象A爲查找一個元對象在收集對 象B裏位置的鑰匙,並支持是否容納,刪除收集對象B裏的元對象這樣的操做,那麼,equals()和hashCode()函數必須開發者本身定義。其餘情 況下,這兩個函數是不須要定義的。

equals():

它是用於進行兩個對象的比較的,是對象內容的比較,固然也能用於進行對象參閱值的比較。什麼是對象參閱值的比較?就是兩個參閱變量的值得比較,咱們 都知道參閱變量的值其實就是一個數字,這個數字能夠當作是鑑別不一樣對象的代號。兩個對象參閱值的比較,就是兩個數字的比較,兩個代號的比較。這種比較是默 認的對象比較方式,在Object這個對象中,這種方式就已經設計好了。因此你也不用本身來重寫,浪費沒必要要的時間。

對象內容的比較纔是設計equals()的真正目的,Java語言對equals()的要求以下,這些要求是必須遵循的。不然,你就不應浪費時間:

•對稱性:若是x.equals(y)返回是「true」,那麼y.equals(x)也應該返回是「true」。

•反射性:x.equals(x)必須返回是「true」。

•類推性:若是x.equals(y)返回是「true」,並且y.equals(z)返回是「true」,那麼z.equals(x)也應該返回是「true」。

•還有一致性:若是x.equals(y)返回是「true」,只要x和y內容一直不變,無論你重複x.equals(y)多少次,返回都是「true」。

•任何狀況下,x.equals(null),永遠返回是「false」;x.equals(和x不一樣類型的對象)永遠返回是「false」。

hashCode():
這個函數返回的就是一個用來進行赫希操做的整型代號,請不要把這個代號和前面所說的參閱變量所表明的代號弄混了。後者不只僅是個代號還具備在內存中才查找對 象的位置的功能。hashCode()所返回的值是用來分類對象在一些特定的收集對象中的位置。這些對象是HashMap, Hashtable, HashSet,等等。這個函數和上面的equals()函數必須本身設計,用來協助HashMap, Hashtable, HashSet,等等對本身所收集的大量對象進行搜尋和定位。

這些收集對象究竟如何工做的,想象每一個元對象hashCode是一個箱子的 編碼,按照編碼,每一個元對象就是根據hashCode()提供的代號納入相應的箱子裏。全部的箱子加起來就是一個HashSet,HashMap,或 Hashtable對象,咱們須要尋找一個元對象時,先看它的代碼,就是hashCode()返回的整型值,這樣咱們找到它所在的箱子,而後在箱子裏,每 個元對象都拿出來一個個和咱們要找的對象進行對比,若是兩個對象的內容相等,咱們的搜尋也就結束。這種操做須要兩個重要的信息,一是對象的 hashCode(),還有一個是對象內容對比的結果。

hashCode()的返回值和equals()的關係以下:

•若是x.equals(y)返回「true」,那麼x和y的hashCode()必須相等。

•若是x.equals(y)返回「false」,那麼x和y的hashCode()有可能相等,也有可能不等。

爲何這兩個規則是這樣的,緣由其實很簡單,拿HashSet來講吧,HashSet能夠擁有一個或更多的箱子,在同一個箱子中能夠有一個 或更多的獨特元對象(HashSet所容納的必須是獨特的元對象)。這個例子說明一個元對象能夠和其餘不一樣的元對象擁有相同的hashCode。可是一個 元對象只能和擁有一樣內容的元對象相等。因此這兩個規則必須成立。

設計這兩個函數所要注意到的:
若是你設計的對象類型並不使用於收集性對象,那麼沒有必要本身再設計這兩個函數的處理方式。這是正確的面向對象設計方法,任何用戶一時用不到的功能,就先不要設計,以避免給往後功能擴展帶來麻煩。

若是你在設計時想別出心裁,不遵照以上的兩套規則,那麼勸你仍是不要作這樣想入非非的事。我尚未遇到過哪個開發者和我說設計這兩個函數要違背前面說的兩個規則,我碰到這些違反規則的狀況時,都是做爲設計錯誤處理。

當一個對象類型做爲收集型對象的元對象時,這個對象應該擁有本身處理equals(),和/或處理hashCode()的設計,並且要遵照前面所說 的兩種原則。equals()先要查null和是不是同一類型。查同一類型是爲了不出現ClassCastException這樣的異常給丟出來。查 null是爲了不出現NullPointerException這樣的異常給丟出來。

若是你的對象裏面容納的數據過多,那麼這兩個函數 equals()和hashCode()將會變得效率低。若是對象中擁有沒法serialized的數據,equals()有可能在操做中出現錯誤。想象 一個對象x,它的一個整型數據是transient型(不能被serialize成二進制數據流)。然而equals()和hashCode()都有依靠 這個整型數據,那麼,這個對象在serialization以前和以後,是否同樣?結果是不同。由於serialization以前的整型數據是有效的 數據,在serialization以後,這個整型數據的值並無存儲下來,再從新由二進制數據流轉換成對象後,二者(對象在serialization 以前和以後)的狀態已經不一樣了。這也是要注意的。

 


在通常的應用中你不須要了解hashcode的用法,但當你用到hashmap,hashset等集合類時要注意下hashcode。

    你想經過一個object的key來拿hashmap的value,hashmap的工做方法是,經過你傳入的object的hashcode在內存中找地址,當找到這個地址後再經過equals方法來比較這個地址中的內容是否和你原來放進去的同樣,同樣就取出value。

    因此這裏要匹配2部分,hashcode和equals但假如說你new一個object做爲key去拿value是永遠得不到結果的,由於每次new一個object,這個object的hashcode是永遠不一樣的,因此咱們要重寫hashcode,你能夠令你的hashcode是object中的一個恆量,這樣永遠能夠經過你的object的hashcode來找到key的地址,而後你要重寫你的equals方法,使內存中的內容也相等。。。

HashMap相關 的源碼以下:


public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);//調用putVal
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//使用對象key的hashCode計算出hash值
    //上文中所說的使用這個hash值去內存中找
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //若是table爲空或者長度爲0,則resize()
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) //對應的hash值在集合中沒找到
        tab[i] = newNode(hash, key, value, null);//new 了一個Node,直接插入。應該是第一個
    else {
        Node<K,V> e; K k;
        //第一個node的hash值即爲要加入元素的hash
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
        
            e = p;
        else if (p instanceof TreeNode)//第一個節點是TreeNode,即tree-bin
       
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {//不是TreeNode,即爲鏈表,遍歷鏈表
            for (int binCount = 0; ; ++binCount) {
             /*到達鏈表的尾端也沒有找到key值相同的節點,使用equals方法判斷
              *則生成一個新的Node,而且判斷鏈表的節點個數是否是到達轉換成紅黑樹的上界
              *達到,則轉換成紅黑樹
              */
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))//判斷是否相等
                    break;
                p = e;
            }
        }
        
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            //返回舊的value值
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
相關文章
相關標籤/搜索