面試的時候,經常會被問到==和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());
輸出的結果是什麼?
從上面結果,我們可以得到如下結論:
當 == 左右兩邊是基本類型的時候,其實就是比較的是數值是否相等;
當 == 左右兩邊是對象(引用)類型的時候,其實比較的是p和p2這兩個對象所指向的堆中的對象地址,一般我們簡稱:比較的是內存地址值。
需要注意:
因爲 Java 只有值傳遞,所以,對於 == 來說,不管是比較基本數據類型,還是引用數據類型的變量,其本質比較的都是值,只是引用類型變量存的值是對象的地址。
來看看equals()
equals()方法特點:
1:equals()方法不能用於判斷基本類型的變量,只能用來判斷兩個對象是否相等。
2:equals()方法存在於Object類中的。而我們又指導Object類是所有類的直接或者間接的父類。所以所有類都具有equals()方法
看看Object源碼中equals()方法:
從源碼中我們可以看出,底層其實使用的是 == 。
== 左右兩邊都是對象。從上面我們知道==比較對象,其實就是比較對象內存中的地址值。
所以,我們可以得到equals()方法存在兩種使用情況的結論:
1:類沒有重寫equals()方法:
當兩個對象沒有重寫equals()方法時候,通過equals()方法進行比較的時候,其實就等價於通過"=="比較兩個對象。因爲在沒有重新equals方法的情況下默認都使用的是Object類的equals()方法;
2:類重寫了equals()方法:
一般在工作中,我們都重寫equals()方法來比較兩個對象中的屬性是否相等。如果兩個對象的屬性相等,則返回true.就認爲兩個對象是相等的。
代碼如下:
定義一個Girl對象,有兩個屬性:樣貌和膚色。然後重寫equals()方法
測試重寫了equals()方法後,兩個girl通過equals比較:
我們來看看輸出的結果:
equal()方法輸入的是:true
但是實際上,兩個Girl對象在堆中的內存地址值不一樣。
我們在Girl對象中添加地址對象屬性,在重寫equals方法:
測試:
結果:
從測試效果來看,可以驗證結論:equals()比較兩個重新equals()方法對象的時候,其實就是比較的是兩個對象中每個屬性值。
現在再來回答 == 和 equals()方法有什麼區別?這個問題應該好回答了吧。
接下來,我們在來看看hashCode()方法
hashcCode是什麼?
我們在調用對象的hashCode()方法的時候,返回的是一個int整數。這個整數其實是散列碼,不過我們習慣稱之爲哈希碼。作用就是確定這個對象在hash表中的所以位置。
出處:
hashCode()方法被定義在Object類中。這也就意味着任何一個類都有hashCode()這個方法(和equals()方法一樣,都是被定義在Object對象中)。查看Object的源碼,我們可以發現,次方法被native關鍵字修飾的。也就是說,Object中的hashCode()方法調用的是本地方法的。其實就是調用操作系統自己的hashCode()方法(用C語言或者是C++語言實現的)。該方法通常用來將對象的內存地址轉換成整數後返回的。
那麼爲什麼要有hashCode?
起始hash存儲的是鍵值對(K-V)形式的,其特點就是:能夠根據"key"快速的檢索出對應的"值"。在快速檢索的時候,就使用到了哈希碼。
回想下hashMap在put對象的時候,先計算出key對應的hashCode值,來判斷對象需要加入的位置。如果不存在,就直接插入,如果存在,就加到鏈表中。如下圖:
從上面我們可以知道,起始 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值卻不相等。如下示例:
我們來看看結果:
總結:
重寫equals()方法是好,必須要重寫hashCode()方法。
思考:重寫equals()方法時候,沒有重寫hashCode()方法的haul,在使用HashMap/HashSet時候可能會出現什麼問題?
我們以hashSet爲例(hashSet底層使用的是hashMap來實現的):
結果:
(꒪ꇴ꒪(꒪ꇴ꒪ ;)哈? 不是說hashSet是唯一的,不能有重複的嗎?打印出來的set集合大小是2啊,不是1啊。
其實,這就是隻重寫了equals(),沒有重寫hashCode()方法的後果。
因爲在set.add()方法時候,先判斷hashcode值,從上圖我們可以看到,兩個對象hashCode值不相等。set就認爲不是一個對象,所以大小就是2了。
so,我們在重寫equals()方法的時候,一定要重寫hashCode()方法