hashCode
和equals
經常在面試中會被問到,在工做中咱們也有可能遇到要重寫對象equals
方法的狀況,並且hashCode
方法的設計思想值得咱們學習,因此咱們有必要去深刻學習一下這兩個方法。java
下面我就以面試問答的形式學習咱們的——hashcode
和equals
方法(源碼分析基於JDK8)android
本文同步發佈於簡書:www.jianshu.com/p/ce3dbf5f0…git
問:hashCode
方法有了解過嗎?這個方法有什麼用?程序員
答:從JAVA官方對hashCode
方法的說明定義(定義在示例代碼中),咱們能夠得知hashCode
的做用有以下幾點:github
hashCode
的存在主要用於查找的快捷性,如Hashtable
,HashMap
等,hashCode
是用來在散列存儲結構中肯定對象的存儲地址的。面試
若是兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode必定要相同。算法
若是對象的equals方法被重寫,那麼對象的hashCode也儘可能重寫,而且產生hashCode使用的對象,必定要和equals方法中使用的一致,不然就會違反上面提到的第2點。編程
兩個對象的hashCode相同,並不必定表示兩個對象就相同,也就是不必定適用於equals(java.lang.Object) 方法,只可以說明這兩個對象在散列存儲結構中,如Hashtable,他們「存放在同一個籃子裏」。數組
1.hashcode是用來查找的,若是你學過數據結構就應該知道,在查找和排序這一章有
例如內存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,
若是不用hashcode而任意存放,那麼當查找時就須要到這八個位置裏挨個去找,或者用二分法一類的算法。
但若是用hashcode那就會使效率提升不少。
咱們這個類中有個字段叫ID,那麼咱們就定義咱們的hashcode爲ID%8,
而後把咱們的類存放在取得得餘數那個位置。好比咱們的ID爲9,
9除8的餘數爲1,那麼咱們就把該類存在1這個位置,
若是ID是13,求得的餘數是5,那麼咱們就把該類放在5這個位置。
這樣,之後在查找該類時就能夠經過ID除 8求餘數直接找到存放的位置了。
2.可是若是兩個類有相同的hashcode怎麼辦那(咱們假設上面的類的ID不是惟一的),
例如9除以8和17除以8的餘數都是1,那麼這是否是合法的,
回答是:能夠這樣。那麼如何判斷呢?在這個時候就須要定義 equals了。
也就是說,咱們先經過 hashcode來判斷兩個類是否存放某個桶裏,
但這個桶裏可能有不少類,那麼咱們就須要再經過 equals 來在這個桶裏找到咱們要的類。
那麼。重寫了equals(),爲何還要重寫hashCode()呢?
想一想,你要在一個桶裏找東西,你必須先要找到這個桶啊,
你不經過重寫hashcode()來找到桶,光重寫equals()有什麼用啊複製代碼
上述回答轉載於:Java中hashCode的做用 因爲做者總結的太好,因此直接轉載了bash
示例代碼:
package java.lang;
public class Object {
·······
/**
* 返回該對象的哈希碼值。
* 支持此方法是爲了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
* {@link java.util.HashMap}.
* <p>
* hashCode 的常規協定是:
* <ul>
* <li>在 Java 應用程序執行期間,在對同一對象屢次調用 hashCode 方法時,
* 必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的信息沒有被修改。
* 從某一應用程序的一次執行到同一應用程序的另外一次執行,該整數無需保持一致。
* <li>若是根據 equals(Object) 方法,兩個對象是相等的,
* 那麼對這兩個對象中的每一個對象調用 hashCode 方法都必須生成相同的整數結果。
* <li>若是根據 equals(java.lang.Object) 方法,兩個對象不相等,
* 那麼對這兩個對象中的任一對象上調用 hashCode 方法不 要求必定生成不一樣的整數結果。
* 可是,程序員應該意識到,爲不相等的對象生成不一樣整數結果能夠提升哈希表的性能。
* </ul>
* <p>
* 實際上,由 Object 類定義的 hashCode 方法確實會針對不一樣的對象返回不一樣的整數。
* (這通常是經過將該對象的內部地址轉換成一個整數來實現的,
* 可是 JavaTM 編程語言不須要這種實現技巧。)
*
* @return 此對象的一個哈希碼值。
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
·······
}複製代碼
問:談談你對equals(Object obj)
方法的理解,它和 ==
操做符相比,有什麼區別?
答:
A.==
操做符分爲兩種狀況:
比較基礎類型(byte,short,int,long,float,double,char,boolean)時,比較的是值是否相等
比較對象,比較的是對象在內存中的空間地址是否相等。
B.equals(Object obj)
方法比較也分爲兩種狀況:
若是一個類沒有重寫equals(Object obj)
方法,則等價於經過==
比較兩個對象,即比較的是對象在內存中的空間地址是否相等。
若是重寫了equals(Object obj)
方法,則根據重寫的方法內容去比較相等,返回true
則相等,false
則不相等。
問:那若是要您去重寫equals(Object obj)
方法,您會怎麼作?重寫的過程須要注意什麼?
答:咱們在重寫equals(Object obj)
方法,須要遵照JAVA官方的通用約定(詳細請看示例代碼),約定簡述:
對於非 null 的對象 x,必定有x.equals(null)=false
當equals(Object obj)
方法被重寫時,一般有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。
根據上述約定,咱們能夠按以下步驟重寫equals(Object obj)
:
1). 先使用 ==
操做符判斷兩個對象的引用地址是否相同。
2). 使用instanceof
來判斷 兩個對象的類型是否一致。
3). 若是類型相同,則把待比較參數轉型,逐一比較兩個對象內部的值是否一致,所有一致才返回true
,不然返回false
。
4). 重寫hashCode
方法,確保相等的兩個對象必須具備相等的哈希碼。
hashCode
方法時,最好是將全部用於相等性檢查的字段都進行hashCode
計算,最後將全部hashCode
值相加,得出最終的hashCode
,這樣能夠保證hashCode生成均勻,不容易產生碰撞。常見數據類型hashcode計算方式以下(參考自JDK源碼):
重要字段var的類型 | hash運算 |
---|---|
byte,short,int,char | (int)var |
long | (int)(var ^ (var >>> 32)) |
float | Float.floatToIntBits(var) |
double | long bits = Double.doubleToLongBits(var);份量 = (int)(bits ^ (bits >>> 32)); |
引用類型 | (null == var ? 0 : var.hashCode()) |
示例代碼:
/**
* 指示其餘某個對象是否與此對象「相等」。
* <p>
* equals 方法在非空對象引用上實現相等關係:
* <ul>
* <li>自反性:對於任何非空引用值 x,x.equals(x) 都應返回 true。
*
* <li>對稱性:對於任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,
* x.equals(y) 才應返回 true。
*
* <li>傳遞性:對於任何非空引用值 x、y 和 z,若是 x.equals(y) 返回 true,
* 而且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true。
*
* <li>一致性:對於任何非空引用值 x 和 y,屢次調用 x.equals(y) 始終返回
* true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。
*
* <li>對於任何非空引用值 x,x.equals(null) 都應返回 false。
* </ul>
*
* <p>
* Object 類的 equals 方法實現對象上差異可能性最大的相等關係;
* 即,對於任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,
* 此方法才返回 true(x == y 具備值 true)。
*
* <p>
* 注意:當此方法被重寫時,一般有必要重寫 hashCode 方法,
* 以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具備相等的哈希碼。
*
* @param 要與之比較的引用對象。
* @return 若是此對象與 obj 參數相同,則返回 true;不然返回 false。
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}複製代碼
問:若是須要您去維護一個類的hash散列表,如何設計,如何解決hash衝突?
答:咱們在設計類的hash散列表時,不能保證每一個元素的hash值都是不同的,這樣就會形成hash衝突。解決hash衝突有以下4種方法:
開發定址法:既然當前位置容不下衝突的元素了,那就再找一個空的位置存儲 Hash 衝突的值(當前 index 衝突了,那麼將衝突的元素放在 index+1)。
再散列法:換一個 Hash 算法再計算一個 hash 值,若是不衝突了就存儲值(例如第一個算法是名字的首字母的 Hash 值,若是衝突了,計算名字的第二個字母的 Hash 值,若是衝突解決了則將值放入數組中)。
鏈地址法:每一個數組中都存有一個單鏈表,發生 Hash 衝突時,只是將衝突的 value 看成新節點插入到鏈表(HashMap 解決衝突的辦法)。
公共溢出區法:將衝突的 value 都存到另一個順序表中,查找時若是當前表沒有對應值,則去溢出區進行順序查找。
equals
方法,這兩點必定要記住:A.若是兩個對象相等(equals() 返回 true),那麼它們的 hashCode()必定要相同;
B.若是兩個對象hashCode()相等,它們並不必定相等(equals() 不必定返回 true)。
若是重寫的equals
方法但不重寫hashCode
,都是耍流氓,會有意想不到的結果。
重寫hashCode
方法時,儘量將全部用於相等比較的參數都參與hashCode的計算。
創建hash散列表的意義就是在於,提升查詢效率,當數據量大時,尤其顯著。
參考文章:
Java中hashCode的做用
如何正確實現 Java 中的 HashCode
Java 的 equals 與 hashcode 對比分析
程序員必須搞清的概念equals和=和hashcode的區別
Android 面試準備之「equals 和 == 」