文章目錄
前言
== 與 equals() 與 hashCode() 的區別與聯繫可以說是 Java 的經典和高頻面試題了。
這個問題好像不難,但是如果沒有好好了解一下,卻是真的很容易被問住。
來個自測:
如果以下這些問題你都可以非常確定的回答出來,那這篇博客可以不用看了;反之,如果你對有些問題還不瞭解,或者模棱兩可,那務必好好把這篇博客看完!
- == 與 equals() 的區別?
- 重寫了 hashCode() 方法後,散列碼(或者稱爲:哈希值)一樣的兩個對象一定相等嗎?
- 如果沒有重寫 hashCode() 方法,散列碼(哈希值)一樣的兩個對象相等嗎?
- 爲什麼重寫了 equals() 方法後,還要重寫 hashCode() 方法? 如果不重寫會出現什麼問題?
下面首先將這3個單獨介紹一下:
一、==
- 對於基本數據類型, == 比較的是值。
- 對於引用數據類型,== 比較的是內存地址。
二、equals()
-
equals() 是 Object 類提供的方法,若未重寫,底層相當於是一個 == 。
-
但一般是使用重寫後的 equals() 的方法,用來比較的兩個對象的屬性值是否相同。
-
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。 -
Java的內置類一般都已經重寫了 equals 方法,比如 String 類。
-
重寫一個 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()
-
hashCode() 方法是 Object 類提供的一個本地方法,用於返回對象的散列碼(hash code),散列碼可以是任意的整數,包括正數和負數。
-
兩個相等的對象要求返回相等的散列碼。
-
Object 默認提供的本地方法 hashCode() 方法,並不一定是將對象的內存地址直接當作散列碼來返回;而是通過該對象的內部地址轉換成的一個整數作爲散列碼返回。
(《Java核心技術 卷Ⅰ》11版中也說了:“每個對象都有一個默認的散列碼,其值由對象的存儲地址得出。”,注意,這裏說的是 “由對象的存儲地址得出”,而不是說 “其值是對象的存儲地址”。)
(其實到底會不會直接返回內存地址當作散列碼取決於運行時庫和JVM的具體實現,OpenJDK默認情況下不是直接返回內存地址作爲散列碼,驗證的話,可以看看這篇博客:Java的Object.hashCode()的返回值到底是不是對象內存地址?) -
覆蓋 equals() 方法時,也要覆蓋 hashcode() 方法,使得如果 x.equals(y) 返回 true ,那麼 x.hashCode() 與 y.hashCode() 就必須返回相同的值。(爲什麼要這樣做?文章後面會解釋。)
-
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() 相關的問題,應該都不在話下了。
如果看完之後,還有相關問題的不知道,可以在評論區留下問題,會及時回答更新。
點個關注,不再迷路
這裏是猿兄,爲你分享程序員的世界。
非常感謝各位優秀的程序員們能看到這裏,如果覺得文章還不錯的話,求點贊👍 求關注💗 求分享👬,對我來說真的 非常有用!!!
注: 如果猿兄這篇博客有任何錯誤和建議,歡迎大家留言,不勝感激!