一文搞懂==、equals和hashCode=的區(qū)別
面試的時候,經(jīng)常會被問到==和equals()的區(qū)別是什么?以及我們也知道重寫equals()時候必須重新hashCode()。這是為什么?既然有了hashCode()方法了,JDK又為什么要提供equals()方法呢?如果在重寫equals()時候沒有重寫hashCode(),在使用HashMap或HashSet的時候可能會出現(xiàn)什么情況?
一文搞懂 == 、equals和hashCode
== 和 equals()的區(qū)別是什么?
先來看看 ==
Java中使用==的時候,如果左右兩邊是基本類型和兩邊是應(yīng)用類型的作用效果是不同的:
我們看看下面如下代碼:
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());
輸出的結(jié)果是什么?

從上面結(jié)果,我們可以得到如下結(jié)論:
當(dāng) == 左右兩邊是基本類型的時候,其實就是比較的是數(shù)值是否相等;
當(dāng) == 左右兩邊是對象(引用)類型的時候,其實比較的是p和p2這兩個對象所指向的堆中的對象地址,一般我們簡稱:比較的是內(nèi)存地址值。
需要注意:
因為 Java 只有值傳遞,所以,對于 == 來說,不管是比較基本數(shù)據(jù)類型,還是引用數(shù)據(jù)類型的變量,其本質(zhì)比較的都是值,只是引用類型變量存的值是對象的地址。
來看看equals()
equals()方法特點:
1:equals()方法不能用于判斷基本類型的變量,只能用來判斷兩個對象是否相等。
2:equals()方法存在于Object類中的。而我們又指導(dǎo)Object類是所有類的直接或者間接的父類。所以所有類都具有equals()方法
看看Object源碼中equals()方法:

從源碼中我們可以看出,底層其實使用的是 == 。
== 左右兩邊都是對象。從上面我們知道==比較對象,其實就是比較對象內(nèi)存中的地址值。
所以,我們可以得到equals()方法存在兩種使用情況的結(jié)論:
1:類沒有重寫equals()方法:
當(dāng)兩個對象沒有重寫equals()方法時候,通過equals()方法進行比較的時候,其實就等價于通過"=="比較兩個對象。因為在沒有重新equals方法的情況下默認都使用的是Object類的equals()方法;
2:類重寫了equals()方法:
一般在工作中,我們都重寫equals()方法來比較兩個對象中的屬性是否相等。如果兩個對象的屬性相等,則返回true.就認為兩個對象是相等的。
代碼如下:
定義一個Girl對象,有兩個屬性:樣貌和膚色。然后重寫equals()方法

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

我們來看看輸出的結(jié)果:

equal()方法輸入的是:true
但是實際上,兩個Girl對象在堆中的內(nèi)存地址值不一樣。
我們在Girl對象中添加地址對象屬性,在重寫equals方法:

測試:
結(jié)果:

從測試效果來看,可以驗證結(jié)論:equals()比較兩個重新equals()方法對象的時候,其實就是比較的是兩個對象中每個屬性值。
現(xiàn)在再來回答 == 和 equals()方法有什么區(qū)別?這個問題應(yīng)該好回答了吧。
接下來,我們在來看看hashCode()方法
hashcCode是什么?
我們在調(diào)用對象的hashCode()方法的時候,返回的是一個int整數(shù)。這個整數(shù)其實是散列碼,不過我們習(xí)慣稱之為哈希碼。作用就是確定這個對象在hash表中的所以位置。
出處:
hashCode()方法被定義在Object類中。這也就意味著任何一個類都有hashCode()這個方法(和equals()方法一樣,都是被定義在Object對象中)。查看Object的源碼,我們可以發(fā)現(xiàn),次方法被native關(guān)鍵字修飾的。也就是說,Object中的hashCode()方法調(diào)用的是本地方法的。其實就是調(diào)用操作系統(tǒng)自己的hashCode()方法(用C語言或者是C++語言實現(xiàn)的)。該方法通常用來將對象的內(nèi)存地址轉(zhuǎn)換成整數(shù)后返回的。

那么為什么要有hashCode?
起始hash存儲的是鍵值對(K-V)形式的,其特點就是:能夠根據(jù)"key"快速的檢索出對應(yīng)的"值"。在快速檢索的時候,就使用到了哈希碼。
回想下hashMap在put對象的時候,先計算出key對應(yīng)的hashCode值,來判斷對象需要加入的位置。如果不存在,就直接插入,如果存在,就加到鏈表中。如下圖:

從上面我們可以知道,起始 hashCode()和equals()這兩個方法都是用于比較兩個對象是否相等的。
問題:既然兩個方法都是比較對象是否相等,那么為什么JDK還要同時提供這兩個方法呢?
答:為了提高效率。
還以hashMap的put方法為例,我們知道,先計算出hashCode,如果不存在,就可以直接put了。不用比較了,少了一次比較。效率就高了。
問題:那么能否只使用hashCode()方法呢?
答:不能。因為我們知道,哈希碼是通過函數(shù)算出來的整數(shù)。既然使用的是公式,那么可能出現(xiàn)兩個對象不一樣,但是哈希碼一樣的。
就比如我們使用 a+b這個公式得出的一個整數(shù)一樣。4+4 = 8;5+3=8;
經(jīng)過公式計算的結(jié)果都是8,但是兩個算式的a和b卻是不相等的。
問題:如果兩個對象的hashCode值相等,它們相等嗎?
答:不相等。如:4+4 = 8;5+3=8;
通過上面說明,我們可以得到hashcode相關(guān)結(jié)論:
1:兩個對象hashcode想的,那么這兩個對象不一樣相等(hash碰撞了。如:4+4 = 8;5+3=8;)
2:如果兩個對象的hashCode值不相等,那么這兩個對象就不相等
通過上面我們分析equals()方法,我們還可以得到下面這個結(jié)論:
3:如果兩個對象的hashCode想的呢并且equals()方法返回的也是true。那么我們才能認為這兩個對象相等的。
因為:4+4 = 8;4+4 = 8; 其中的8就是hashCode. 兩個算式的 a、b都是4,也是相等的。
問題:為什么重寫equals()時候必須重寫hashCode()方法?
因為一般在重寫equals()方法的時候,是要對兩個對象進行比較的。如果兩個對象相等的話,hashCode值必須相等,equals()方法判斷兩個對象也是相等的。
如果重寫equals()方法時候,沒有重寫hashCode()方法的話,可能導(dǎo)致equals()方法判斷想的的兩個對象hashCCode值卻不相等。如下示例:

我們來看看結(jié)果:

總結(jié):
重寫equals()方法是好,必須要重寫hashCode()方法。
思考:重寫equals()方法時候,沒有重寫hashCode()方法的haul,在使用HashMap/HashSet時候可能會出現(xiàn)什么問題?
我們以hashSet為例(hashSet底層使用的是hashMap來實現(xiàn)的):

結(jié)果:

(???(??? ;)哈? 不是說hashSet是唯一的,不能有重復(fù)的嗎?打印出來的set集合大小是2啊,不是1啊。
其實,這就是只重寫了equals(),沒有重寫hashCode()方法的后果。
因為在set.add()方法時候,先判斷hashcode值,從上圖我們可以看到,兩個對象hashCode值不相等。set就認為不是一個對象,所以大小就是2了。
so,我們在重寫equals()方法的時候,一定要重寫hashCode()方法
