一次性搞清楚equals和hashCode

前言

在程序設計中,有不少的「公約」,遵照約定去實現你的代碼,會讓你避開不少坑,這些公約是前人總結出來的設計規範。java

Object類是Java中的萬類之祖,其中,equals和hashCode是2個很是重要的方法。程序員

這2個方法老是被人放在一塊兒討論。最近在看集合框架,爲了打基礎,就決定把一些細枝末節清理掉。一次性搞清楚!算法

下面開始剖析。框架

public boolean equals(Object obj)

Object類中默認的實現方式是 : return this == obj 。那就是說,只有this 和 obj引用同一個對象,纔會返回true。函數

Object 的equals方法this

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

而咱們每每須要用equals來判斷 2個對象是否等價,而非驗證他們的惟一性。這樣咱們在實現本身的類時,就要重寫equals.設計

注: 重寫equals的緣由:是驗證對象是否等價,而非驗證他們的惟一性。code

按照約定,equals要知足如下規則。對象

  • 自反性: x.equals(x) 必定是true遞歸

  • 對null: x.equals(null) 必定是false

  • 對稱性: x.equals(y) 和 y.equals(x)結果一致

  • 傳遞性: a 和 b equals , b 和 c equals,那麼 a 和 c也必定equals。

  • 一致性: 在某個運行時期間,2個對象的狀態的改變不會不影響equals的決策結果,那麼,在這個運行時期間,不管調用多少次equals,都返回相同的結果。

一個例子

class Test
{
    private int num;
    private String data;

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

        if ((obj == null) || (obj.getClass() != this.getClass()))
            return false;

           //能執行到這裏,說明obj和this同類且非null。
        Test test = (Test) obj;
         
        //data == test.data  這裏爲了說明String常量池的存在。
        return num == test.num&& (data == test.data || (data != null && data.equals(test.data)));
    }

    public int hashCode()
    {
        //重寫equals,也必須重寫hashCode。具體後面介紹。
    }

}

equals編寫指導

Test類對象有2個字段,num和data,這2個字段表明了對象的狀態,他們也用在equals方法中做爲評判的依據。

在第8行,傳入的比較對象的引用和this作比較,這樣作是爲了 save time ,節約執行時間,若是this 和 obj是 對同一個堆對象的引用,那麼,他們必定是qeuals 的。

接着,判斷obj是否是爲null,若是爲null,必定不equals,由於既然當前對象this能調用equals方法,那麼它必定不是null,非null 和 null固然不等價。

而後,比較2個對象的運行時類,是否爲同一個類。不是同一個類,則不equals。getClass返回的是 this 和obj的運行時類的引用。若是他們屬於同一個類,則返回的是同一個運行時類的引用。注意,一個類也是一個對象。

一、有些程序員使用下面的第二種寫法替代第一種比較運行時類的寫法。應該避免這樣作。

if((obj == null) || (obj.getClass() != this.getClass())) 
   
     return false; 


if(!(obj instanceof Test)) 
    
     return false; // avoid 避免!

它違反了公約中的對稱原則。

它違反了公約中的對稱原則。

例如:假設Dog擴展了Aminal類。

dog instanceof Animal 獲得true

animal instanceof Dog 獲得false

這就會致使

animal.equls(dog) 返回true dog.equals(animal) 返回false

僅當Test類沒有子類的時候,這樣作才能保證是正確的。

二、按照第一種方法實現,那麼equals只能比較同一個類的對象,不一樣類對象永遠是false。但這並非強制要求的。通常咱們也不多須要在不一樣的類之間使用equals。

三、在具體比較對象的字段的時候,對於基本值類型的字段,直接用 == 來比較(注意浮點數的比較,這是一個坑)對於引用類型的字段,你能夠調用他們的equals,固然,你也須要處理字段爲null 的狀況。對於浮點數的比較,我在看Arrays.binarySearch的源代碼時,發現了以下對於浮點數的比較的技巧:

if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double類型

if(  Float.floatToIntBits(f1) == Float.floatToIntBits(f2)  )      //f1 和 f2 是d2是float類型

四、並不老是要將對象的全部字段來做爲equals 的評判依據,那取決於你的業務要求。好比你要作一個家電功率統計系統,若是2個家電的功率同樣,那就有足夠的依據認爲這2個家電對象等價了,至少在你這個業務邏輯背景下是等價的,並不關心他們的價錢啊,品牌啊,大小等其餘參數。

五、最後須要注意的是,equals 方法的參數類型是Object,不要寫錯!

public int hashCode()

這個方法返回對象的散列碼,返回值是int類型的散列碼。 對象的散列碼是爲了更好的支持基於哈希機制的Java集合類,例如 Hashtable, HashMap, HashSet 等。

關於hashCode方法,一致的約定是: 重寫了euqls方法的對象必須同時重寫hashCode()方法。

若是2個對象經過equals調用後返回是true,那麼這個2個對象的hashCode方法也必須返回一樣的int型散列碼

若是2個對象經過equals返回false,他們的hashCode返回的值容許相同。(然而,程序員必須意識到,hashCode返回獨一無二的散列碼,會讓存儲這個對象的hashtables更好地工做。)

在上面的例子中,Test類對象有2個字段,num和data,這2個字段表明了對象的狀態,他們也用在equals方法中做爲評判的依據。那麼, 在hashCode方法中,這2個字段也要參與hash值的運算,做爲hash運算的中間參數。這點很關鍵,這是爲了遵照:2個對象equals,那麼 hashCode必定相同規則。

也是說,參與equals函數的字段,也必須都參與hashCode 的計算。

合乎情理的是:同一個類中的不一樣對象返回不一樣的散列碼。典型的方式就是根據對象的地址來轉換爲此對象的散列碼,可是這種方式對於Java來講並非惟一的要求的 的實現方式。一般也不是最好的實現方式。

相比 於 equals公認實現約定,hashCode的公約要求是很容易理解的。有2個重點是hashCode方法必須遵照的。約定的第3點,其實就是第2點的 細化,下面咱們就來看看對hashCode方法的一致約定要求。

第一:在某個運行時期間,只要對象的(字段的)變化不會影響equals方法的決策結果,那麼,在這個期間,不管調用多少次hashCode,都必須返回同一個散列碼。

第二:經過equals調用返回true 的2個對象的hashCode必定同樣。

第三:經過equasl返回false 的2個對象的散列碼不須要不一樣,也就是他們的hashCode方法的返回值容許出現相同的狀況。

總結一句話:等價的(調用equals返回true)對象必須產生相同的散列碼。不等價的對象,不要求產生的散列碼不相同。

hashCode編寫指導

在編寫hashCode時,你須要考慮的是,最終的hash是個int值,而不能溢出。不一樣的對象的hash碼應該儘可能不一樣,避免hash衝突。

那麼若是作到呢?下面是解決方案。

一、定義一個int類型的變量 hash,初始化爲 7。

接下來讓你認爲重要的字段(equals中衡量相等的字段)參入散列運,算每個重要字段都會產生一個hash份量,爲最終的hash值作出貢獻(影響)

輸入圖片說明

最後把全部的份量都總和起來,注意並非簡單的相加。選擇一個倍乘的數字31,參與計算。而後不斷地遞歸計算,直到全部的字段都參與了。

int hash = 7;

hash = 31 * hash + 字段1貢獻份量;

hash = 31 * hash + 字段2貢獻份量;

.....

return hash;

重寫equals 必定要重寫 hashcode的緣由

首先須要明確 equals 與hashcode的關係

一、若是兩個對象相同(即用equals比較返回true),那麼它們的hashCode值必定要相同;

二、若是兩個對象的hashCode相同,它們並不必定相同(即用equals比較返回false)

自個人理解:

因爲爲了提升程序的效率才實現了hashcode方法,先進行hashcode的比較,若是不一樣,那沒就沒必要在進行equals的比較了,這樣就大大減小了equals比較的次數,這對比須要比較的數量很大的效率提升是很明顯的,一個很好的例子就是在集合中的使用;

咱們都知道java中的List集合是有序的,所以是能夠重複的,而set集合是無序的,所以是不能重複的,那麼怎麼能保證不能被放入重複的元素呢,但靠equals方法同樣比較的話,若是原來集合中之後又10000個元素了,那麼放入10001個元素,難道要將前面的全部元素都進行比較,看看是否有重複,歐碼噶的,這個效率可想而知,所以hashcode就應遇而生了,java就採用了hash表,利用哈希算法(也叫散列算法),就是將對象數據根據該對象的特徵使用特定的算法將其定義到一個地址上,那麼在後面定義進來的數據只要看對應的hashcode地址上是否有值,那麼就用equals比較,若是沒有則直接插入,只要就大大減小了equals的使用次數,執行效率就大大提升了。

繼續上面的話題,爲何必需要重寫hashcode方法,其實簡單的說就是爲了保證同一個對象,保證在equals相同的狀況下hashcode值一定相同,若是重寫了equals而未重寫hashcode方法,可能就會出現兩個沒有關係的對象equals相同的(由於equal都是根據對象的特徵進行重寫的),但hashcode確實不相同的。

相關文章
相關標籤/搜索