一文搞懂 == 、equals和hashCode

面試的時候,經常會被問到==和equals()的區別是什麼?以及我們也知道重寫equals()時候必須重新hashCode()。這是爲什麼?既然有了hashCode()方法了,JDK又爲什麼要提供equals()方法呢?如果在重寫equals()時候沒有重寫hashCode(),在使用HashMap或HashSet的時候可能會出現什麼情況?

一文搞懂 == 、equals和hashCode

== 和 equals()的區別是什麼?

先來看看 == 

Java中使用==的時候,如果左右兩邊是基本類型和兩邊是應用類型的作用效果是不同的:

我們看看下面如下代碼:

int x = 128;
int y = 128;
Person p = new Person(new Address("北京"));
Person p2 = p.clone();
System.out.println("兩個基本類型==後值:");
System.out.println(x==y);
System.out.println("兩個對象(引用類型)==後值:");
System.out.println(p == p2);
System.out.println(" \n p的地址值爲:"+p +" \n p2的地址值爲:"+p2.toString());

輸出的結果是什麼?

7541e21e96e80d99b39fc9fb582be914.png

從上面結果,我們可以得到如下結論:

當 == 左右兩邊是基本類型的時候,其實就是比較的是數值是否相等;

當 == 左右兩邊是對象(引用)類型的時候,其實比較的是p和p2這兩個對象所指向的堆中的對象地址,一般我們簡稱:比較的是內存地址值。

需要注意:

因爲 Java 只有值傳遞,所以,對於 == 來說,不管是比較基本數據類型,還是引用數據類型的變量,其本質比較的都是值,只是引用類型變量存的值是對象的地址。

來看看equals()

equals()方法特點:

1:equals()方法不能用於判斷基本類型的變量,只能用來判斷兩個對象是否相等。

2:equals()方法存在於Object類中的。而我們又指導Object類是所有類的直接或者間接的父類。所以所有類都具有equals()方法

看看Object源碼中equals()方法:

468328c746dd09b45dd88aa092324033.png

從源碼中我們可以看出,底層其實使用的是 == 。

== 左右兩邊都是對象。從上面我們知道==比較對象,其實就是比較對象內存中的地址值。

所以,我們可以得到equals()方法存在兩種使用情況的結論:

1:類沒有重寫equals()方法:

當兩個對象沒有重寫equals()方法時候,通過equals()方法進行比較的時候,其實就等價於通過"=="比較兩個對象。因爲在沒有重新equals方法的情況下默認都使用的是Object類的equals()方法;

2:類重寫了equals()方法:

一般在工作中,我們都重寫equals()方法來比較兩個對象中的屬性是否相等。如果兩個對象的屬性相等,則返回true.就認爲兩個對象是相等的。

 

代碼如下:

定義一個Girl對象,有兩個屬性:樣貌和膚色。然後重寫equals()方法

f9f2934e8819f4260dfbf4b1295270e5.png

測試重寫了equals()方法後,兩個girl通過equals比較:

6f56db2b69ac257e34bfd11de1a81471.png

我們來看看輸出的結果:

fe6808d4206e84e913ab74ee8c5329ca.png

equal()方法輸入的是:true

但是實際上,兩個Girl對象在堆中的內存地址值不一樣。

我們在Girl對象中添加地址對象屬性,在重寫equals方法:

2a8fde8768dcad5e26336b9503c8fb14.png

測試:

3fc68e667b99a066ebb7bc10e489577f.png結果:

9252c5e8101e2d40bd2b9a9de5437aea.png

從測試效果來看,可以驗證結論:equals()比較兩個重新equals()方法對象的時候,其實就是比較的是兩個對象中每個屬性值。

現在再來回答 == 和 equals()方法有什麼區別?這個問題應該好回答了吧。

接下來,我們在來看看hashCode()方法

hashcCode是什麼?

我們在調用對象的hashCode()方法的時候,返回的是一個int整數。這個整數其實是散列碼,不過我們習慣稱之爲哈希碼。作用就是確定這個對象在hash表中的所以位置。

出處:

hashCode()方法被定義在Object類中。這也就意味着任何一個類都有hashCode()這個方法(和equals()方法一樣,都是被定義在Object對象中)。查看Object的源碼,我們可以發現,次方法被native關鍵字修飾的。也就是說,Object中的hashCode()方法調用的是本地方法的。其實就是調用操作系統自己的hashCode()方法(用C語言或者是C++語言實現的)。該方法通常用來將對象的內存地址轉換成整數後返回的。

29f9a0563db269f0771405fd9aaf5e9c.png

那麼爲什麼要有hashCode?

起始hash存儲的是鍵值對(K-V)形式的,其特點就是:能夠根據"key"快速的檢索出對應的"值"。在快速檢索的時候,就使用到了哈希碼。

回想下hashMap在put對象的時候,先計算出key對應的hashCode值,來判斷對象需要加入的位置。如果不存在,就直接插入,如果存在,就加到鏈表中。如下圖:

18f669b1062cba88212d684dbc69fc4d.png

從上面我們可以知道,起始 hashCode()和equals()這兩個方法都是用於比較兩個對象是否相等的。

問題:既然兩個方法都是比較對象是否相等,那麼爲什麼JDK還要同時提供這兩個方法呢?

答:爲了提高效率。

還以hashMap的put方法爲例,我們知道,先計算出hashCode,如果不存在,就可以直接put了。不用比較了,少了一次比較。效率就高了。

問題:那麼能否只使用hashCode()方法呢?

答:不能。因爲我們知道,哈希碼是通過函數算出來的整數。既然使用的是公式,那麼可能出現兩個對象不一樣,但是哈希碼一樣的。

就比如我們使用 a+b這個公式得出的一個整數一樣。4+4 = 8;5+3=8;

經過公式計算的結果都是8,但是兩個算式的a和b卻是不相等的。

問題:如果兩個對象的hashCode值相等,它們相等嗎?

答:不相等。如:4+4 = 8;5+3=8;

通過上面說明,我們可以得到hashcode相關結論:

1:兩個對象hashcode想的,那麼這兩個對象不一樣相等(hash碰撞了。如:4+4 = 8;5+3=8;)

2:如果兩個對象的hashCode值不相等,那麼這兩個對象就不相等

通過上面我們分析equals()方法,我們還可以得到下面這個結論:

3:如果兩個對象的hashCode想的呢並且equals()方法返回的也是true。那麼我們才能認爲這兩個對象相等的。

因爲:4+4 = 8;4+4 = 8; 其中的8就是hashCode. 兩個算式的 a、b都是4,也是相等的。

問題:爲什麼重寫equals()時候必須重寫hashCode()方法?

因爲一般在重寫equals()方法的時候,是要對兩個對象進行比較的。如果兩個對象相等的話,hashCode值必須相等,equals()方法判斷兩個對象也是相等的。

如果重寫equals()方法時候,沒有重寫hashCode()方法的話,可能導致equals()方法判斷想的的兩個對象hashCCode值卻不相等。如下示例:

f4442fdc6899d026a965c25934f10008.png

我們來看看結果:

62a37b24a842a6f3fd27d598146697b3.png

總結:

重寫equals()方法是好,必須要重寫hashCode()方法。

 

思考:重寫equals()方法時候,沒有重寫hashCode()方法的haul,在使用HashMap/HashSet時候可能會出現什麼問題?

我們以hashSet爲例(hashSet底層使用的是hashMap來實現的):

e6a389439c4e3e6b6c5e278c856baf02.png

結果:

748a5e25f229efc76756d145545977c3.png

(꒪ꇴ꒪(꒪ꇴ꒪ ;)哈? 不是說hashSet是唯一的,不能有重複的嗎?打印出來的set集合大小是2啊,不是1啊。

其實,這就是隻重寫了equals(),沒有重寫hashCode()方法的後果。

因爲在set.add()方法時候,先判斷hashcode值,從上圖我們可以看到,兩個對象hashCode值不相等。set就認爲不是一個對象,所以大小就是2了。

so,我們在重寫equals()方法的時候,一定要重寫hashCode()方法

 

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