“==”、“equals()”、“hashcode()”之間的祕密

深夜裡的程式猿發表於2019-04-02

前言

萬丈高樓平地起,今天的聊點基礎而又經常讓人忽視的話題,比如“==”與“equals()”區別?為何當我們重寫完"equals()"後也要有必要去重寫"hashcode()"呢? ... 帶著這些問題,我們一起來探究一下。

概念

"==":它主要是判斷符號兩邊的“物件”的值是否相等,而這裡的“值“”又有所區分了。

基礎資料型別:比較的就是自身的值,這個跟我們常規的理解是基本一致的。

引用資料型別:比較的物件的記憶體地址。

“equals()”:它也是用來判斷兩個物件是否相等,所以也得分不同的情況來說明。

在當前類中,沒有重寫equals方法的話,預設的實現跟"=="的實現是一樣的。下面是Object類的equals方法實現。

在當前類中,重寫了equals方法,此時判斷的依據就是你重寫的邏輯。

怎樣重寫equals()方法?

  • 1、自反性:對於任何非空引用x,x.equals(x)應該返回true。
  • 2、對稱性:對於任何引用x和y,如果x.equals(y)返回true,那麼y.equals(x)也應該返回true。
  • 3、傳遞性:對於任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那麼x.equals(z)也應該返回true。
  • 4、一致性:如果x和y引用的物件沒有發生變化,那麼反覆呼叫x.equals(y)應該返回同樣的結果。
  • 5、非空性:對於任意非空引用x,x.equals(null)應該返回false。

由此可以看出,重寫一個equals()方法,需要注意的點還是比較多的,這裡給出一個參考的事例。

public class EqualsDemo {
    private String name;
    private String info;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        EqualsDemo that = (EqualsDemo) o;

        if (name != null ? !name.equals(that.name) : that.name != null) return false;
        return info != null ? info.equals(that.info) : that.info == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (info != null ? info.hashCode() : 0);
        return result;
    }
}
複製程式碼

有些讀者可能會感到奇怪,不是說重寫equals()方法嗎,為什麼這裡又出現了一個hashcode()?所以這裡又引出了我們的另一個主角hashcode()方法,當我們重寫了equals()方法後,它就一定會出現,也會“吵著“自己也要被重寫。

什麼是hashcode()?

hashCode() 的作用是獲取雜湊碼,也稱為雜湊碼;它返回的一個int整數。這個雜湊碼的作用是確定該物件在雜湊表中的索引位置。hashCode方法的主要作用是為了配合基於雜湊的集合一起正常執行,這樣的雜湊集合包括HashSet、HashMap、HashTable等。它定義在JDK的Object.java中,這就意味著Java中的任何類都包含有hashCode() 函式。

當我們在上面的集合插入物件的時候,java是怎麼知道里面是否有重複的物件呢?可能大家第一反應是equals方法,沒錯這方法可以實現這個功能,但是當集合裡面有成千上萬個元素的時候,效率會如何呢?答案當然是比較差了,所以才會出現了雜湊碼。

public V put(K key, V value) {
    //判斷當前陣列是否等於{},若是則初始化陣列
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //判斷 key 是否等於 null,是則將把當前鍵值對新增進table[0]中,遍歷table[0]連結串列
        //如果已經有null為key的Entry,則修改值,返回舊值,若無則直接新增。
        if (key == null)
            return putForNullKey(value);
        //key不為null則計算hash
        int hash = hash(key);
        //搜尋對應hash所在的table中的索引
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //修改次數
        modCount++;
        addEntry(hash, key, value, i);
        return null;
}
複製程式碼

這裡是jdk7中 Hashmap put()方法的實現,通過原始碼的註釋可以看出執行的流程,需要更詳細的瞭解HashMap可以參考我之前發在開源中國的部落格《Java7 HashMap全面解讀! 》,連結:my.oschina.net/19921228/bl…

經過概念的介紹,知道為什麼重寫完equals()後要接著重寫hashcode()了吧?

People p1=new People("小明",18);
People p2=new People("小明",18);
複製程式碼

此時重寫了equals方法,p1.equals(p2)一定返回true,假如只重寫equals而不重寫hashcode,那麼Student類的hashcode方法就是Object預設的hashcode方法,由於預設的hashcode方法是根據物件的記憶體地址經雜湊演算法得來的,顯然此時s1!=s2,故兩者的hashcode不一定相等。所以在一些集合的使用當中會出現問題。

總結

小小的幾個方法,沒想到卻有這麼多“坑”,而且在面試中也會經常被問到,在金三銀四的時候,但願各位不會陷在這裡。

“==”、“equals()”、“hashcode()”之間的祕密

喜歡的話,關注一下微信公眾號《深夜裡的程式猿》,每天更新高質量IT文章~

相關文章