你真的懂了 == 與 equals() 與 hashCode() 的區別與聯繫了嗎?

前言

== 與 equals() 與 hashCode() 的區別與聯繫可以說是 Java 的經典和高頻面試題了。
這個問題好像不難,但是如果沒有好好了解一下,卻是真的很容易被問住。
來個自測:
如果以下這些問題你都可以非常確定的回答出來,那這篇博客可以不用看了;反之,如果你對有些問題還不瞭解,或者模棱兩可,那務必好好把這篇博客看完!

  1. == 與 equals() 的區別?
  2. 重寫了 hashCode() 方法後,散列碼(或者稱爲:哈希值)一樣的兩個對象一定相等嗎?
  3. 如果沒有重寫 hashCode() 方法,散列碼(哈希值)一樣的兩個對象相等嗎?
  4. 爲什麼重寫了 equals() 方法後,還要重寫 hashCode() 方法? 如果不重寫會出現什麼問題?

下面首先將這3個單獨介紹一下:

一、==

  1. 對於基本數據類型, == 比較的是值。
  2. 對於引用數據類型,== 比較的是內存地址。

二、equals()

  1. equals() 是 Object 類提供的方法,若未重寫,底層相當於是一個 == 。

  2. 但一般是使用重寫後的 equals() 的方法,用來比較的兩個對象的屬性值是否相同。

  3. Java 語言規範要求 equals 方法要滿足以下五點:
    自反性: 對於任何非空引用 x ,x.equals(x) 應該返回 true 。
    對稱性: 對於任何引用 x 和 y ,當且僅當 x.equals(y) 返回 true 時, y.equals(x) 返回 true 。
    傳遞性: 對於任何引用 x、y 和 z ,如果 x.equals(y) 返回 true , y.equals(z) 返回 true ,那麼 x.equals(z) 也應該返回 true 。
    一致性: 如果 x 和 y 引用的對象沒有發生變化,那麼反覆多次調用 x.equals(y) 應該返回同樣的結果。
    對於任何非空引用 x , x.equals(null) 應該返回false。

  4. Java的內置類一般都已經重寫了 equals 方法,比如 String 類。

  5. 重寫一個 equals() 方法
    ① 檢查是否是同一個對象的引用,如果是的話直接返回 true 。
    ② 檢查是否是同一個類型,如果不是的話,直接返回 false 。
    ③ 將對象強制轉換爲相應類類型的變量。
    ④ 判斷兩個對象每個字段是否都相等,基本類型字段用 == 比較,對象字段用 equals 比較;如果所有字段都相等,返回 true ,否則返回 false 。

public class Student {
    private int id;
    private String name;
    private String age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id &&
                Objects.equals(name, student.name) &&
                Objects.equals(age, student.age);
    }
}

三、hashCode()

  1. hashCode() 方法是 Object 類提供的一個本地方法,用於返回對象的散列碼(hash code),散列碼可以是任意的整數,包括正數和負數。

  2. 兩個相等的對象要求返回相等的散列碼。

  3. Object 默認提供的本地方法 hashCode() 方法,並不一定是將對象的內存地址直接當作散列碼來返回;而是通過該對象的內部地址轉換成的一個整數作爲散列碼返回。
    (《Java核心技術 卷Ⅰ》11版中也說了:“每個對象都有一個默認的散列碼,其值由對象的存儲地址得出。”,注意,這裏說的是 “由對象的存儲地址得出”,而不是說 “其值是對象的存儲地址”。)
    (其實到底會不會直接返回內存地址當作散列碼取決於運行時庫和JVM的具體實現,OpenJDK默認情況下不是直接返回內存地址作爲散列碼,驗證的話,可以看看這篇博客:Java的Object.hashCode()的返回值到底是不是對象內存地址?)

  4. 覆蓋 equals() 方法時,也要覆蓋 hashcode() 方法,使得如果 x.equals(y) 返回 true ,那麼 x.hashCode() 與 y.hashCode() 就必須返回相同的值。(爲什麼要這樣做?文章後面會解釋。)

  5. Java的內置對象,一般都重寫了

四、知道了 == 、equals()、hashCode() 基本概念之後,我們來看看博客開始的幾個問題:

4.1 == 與 equals() 的區別?

(這個不難,根據上面的內容,分別說出 == 與 equals 的概念也就是說出了區別了。)

4.2 重寫了 hashCode() 方法之後,散列碼一樣的兩個對象一定相等嗎?

重寫了 hashCode() 方法之後,不同的兩個對象的散列值可能相同。因爲基於方法的實現,不同的對象經過方法計算出的值可能一樣(假設 計算方法是 hash%3,而 5%3=2 ,8%3=2 ,兩個不同的 hash%2 取餘都是 2 )。

再比如,我們來看一下 Java的 String 類中的 hashCode() 的實現:

String類的 hashCode() 就會出現不同的對象,但是散列值不同:

4.3 如果沒有重寫 hashCode() 方法散列碼一樣的兩個對象一定相等嗎?

如果沒有重寫 hashCode() 方法,不同的兩個對象的散列值依舊可能相同;意思也就是說,散列值相同的兩個對象可能不是同一個對象。因爲 Object 類的默認的 hashCode 方法會從對象的存儲地址得出散列碼,它是通過對象的內存地址轉換成一個整數來實現的,而不是直接將內存地址當作散列值。

4.4 爲什麼重寫了 equals() 方法,還要重寫 hashCode() 方法?如果不重寫會出現什麼問題?

如果只重寫 equals 方法,而不重寫 hashCode() 方法,在向集中添加元素時,可能會使集中出現相同值的元素。

下面來測試一下看看:

① equals() 和 hashCode() 方法都重寫

首先我們寫一個學生類,有兩個字段(id 和 name),重寫 equals() 和 hashCode() 方法,爲了方便我們查看結果,順便把 toString方法重寫一下。

然後我們在測試類中去測試一下:

可以看到,,雖然 student1 與 student2 兩個對象的是兩個不同的對象,但是他們的 id 和 name 都一樣,重寫了 equals() 和 hashCode() 方法後 ,這兩個對象調用 equasls 比較返回的是true,並且這兩個對象的散列碼也一樣;插入到 studentSet 集中時,也只能插入一個。

②只重寫 equals() 方法,不重寫 hashCode() 方法

還是剛剛的代碼,但是這次我們只重寫了 equals() 方法,而沒有重寫 hashCode() 方法。

同樣的測試類再測試一下:

這兩個對象調用 equals() 方法返回的還是 true,但是這兩個的散列碼卻不同,插入到集中的時候,明明兩個對象的字段值一模一樣,但是可以插入到一個集中!!這就違反了集的定義了(集中存放的是不重複的數據)。
不僅集 hashSet 中可能會遇到這樣的問題,其他用到 散列碼 的地方也可能會遇到這樣的問題。

所以如果重寫了 equals() 方法,務必記得還要重寫 hashCode() 方法,不如你的程序可能會出現意想不到的問題!


瞭解了這些之後,回答 == 與 equals() 與 hashCode() 相關的問題,應該都不在話下了。

如果看完之後,還有相關問題的不知道,可以在評論區留下問題,會及時回答更新。

點個關注,不再迷路

這裏是猿兄,爲你分享程序員的世界。

非常感謝各位優秀的程序員們能看到這裏,如果覺得文章還不錯的話,求點贊👍 求關注💗 求分享👬,對我來說真的 非常有用!!!

注: 如果猿兄這篇博客有任何錯誤和建議,歡迎大家留言,不勝感激!

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