前言
hashCode和equals常常在面試中會被問到,在工作中我們也有可能遇到要重寫對象equals方法的情況,而且hashCode方法的設(shè)計思想值得我們學(xué)習(xí),所以我們有必要去深入學(xué)習(xí)一下這兩個方法。
下面我就以面試問答的形式學(xué)習(xí)我們的——hashcode和equals方法(源碼分析基于JDK8)
問答內(nèi)容
1.
問:hashCode方法有了解過嗎?這個方法有什么用?
答:從JAVA官方對hashCode方法的說明定義(定義在示例代碼中),我們可以得知hashCode的作用有如下幾點(diǎn):
hashCode的存在主要用于查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結(jié)構(gòu)中確定對象的存儲地址的。如果兩個對象相同,就是適用于equals(java.lang.Object) 方法,那么這兩個對象的hashCode一定要相同。
如果對象的equals方法被重寫,那么對象的hashCode也盡量重寫,并且產(chǎn)生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點(diǎn)。
兩個對象的hashCode相同,并不一定表示兩個對象就相同,也就是不一定適用于equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結(jié)構(gòu)中,如Hashtable,他們“存放在同一個籃子里”。
1.hashcode是用來查找的,如果你學(xué)過數(shù)據(jù)結(jié)構(gòu)就應(yīng)該知道,在查找和排序這一章有
例如內(nèi)存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,
如果不用hashcode而任意存放,那么當(dāng)查找時就需要到這八個位置里挨個去找,或者用二分法一類的算法。
但如果用hashcode那就會使效率提高很多。
我們這個類中有個字段叫ID,那么我們就定義我們的hashcode為ID%8,
然后把我們的類存放在取得得余數(shù)那個位置。比如我們的ID為9,
9除8的余數(shù)為1,那么我們就把該類存在1這個位置,
如果ID是13,求得的余數(shù)是5,那么我們就把該類放在5這個位置。
這樣,以后在查找該類時就可以通過ID除 8求余數(shù)直接找到存放的位置了。
2.但是如果兩個類有相同的hashcode怎么辦那(我們假設(shè)上面的類的ID不是唯一的),
例如9除以8和17除以8的余數(shù)都是1,那么這是不是合法的,
回答是:可以這樣。那么如何判斷呢?在這個時候就需要定義 equals了。
也就是說,我們先通過 hashcode來判斷兩個類是否存放某個桶里,
但這個桶里可能有很多類,那么我們就需要再通過 equals 來在這個桶里找到我們要的類。
那么。重寫了equals(),為什么還要重寫hashCode()呢?
想想,你要在一個桶里找東西,你必須先要找到這個桶啊,
你不通過重寫hashcode()來找到桶,光重寫equals()有什么用啊
上述回答轉(zhuǎn)載于:Java中hashCode的作用 由于作者總結(jié)的太好,所以直接轉(zhuǎn)載了
示例代碼:
package java.lang;
public class Object {
·······
/**
* 返回該對象的哈希碼值。
* 支持此方法是為了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
* {@link java.util.HashMap}.
* <p>
* hashCode 的常規(guī)協(xié)定是:
* <ul>
* <li>在 Java 應(yīng)用程序執(zhí)行期間,在對同一對象多次調(diào)用 hashCode 方法時,
* 必須一致地返回相同的整數(shù),前提是將對象進(jìn)行 equals 比較時所用的信息沒有被修改。
* 從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無需保持一致。
* <li>如果根據(jù) equals(Object) 方法,兩個對象是相等的,
* 那么對這兩個對象中的每個對象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。
* <li>如果根據(jù) equals(java.lang.Object) 方法,兩個對象不相等,
* 那么對這兩個對象中的任一對象上調(diào)用 hashCode 方法不 要求一定生成不同的整數(shù)結(jié)果。
* 但是,程序員應(yīng)該意識到,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能。
* </ul>
* <p>
* 實(shí)際上,由 Object 類定義的 hashCode 方法確實(shí)會針對不同的對象返回不同的整數(shù)。
* (這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實(shí)現(xiàn)的,
* 但是 JavaTM 編程語言不需要這種實(shí)現(xiàn)技巧。)
*
* @return 此對象的一個哈希碼值。
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode();
·······
}
2.
問:談?wù)勀銓?code>equals(Object obj)方法的理解,它和 == 操作符相比,有什么區(qū)別?
答:
A.== 操作符分為兩種情況:
比較基礎(chǔ)類型(byte,short,int,long,float,double,char,boolean)時,比較的是值是否相等
比較對象,比較的是對象在內(nèi)存中的空間地址是否相等。
B.equals(Object obj)方法比較也分為兩種情況:
如果一個類沒有重寫
equals(Object obj)方法,則等價于通過==比較兩個對象,即比較的是對象在內(nèi)存中的空間地址是否相等。如果重寫了
equals(Object obj)方法,則根據(jù)重寫的方法內(nèi)容去比較相等,返回true則相等,false則不相等。
3.
問:那如果要您去重寫equals(Object obj)方法,您會怎么做?重寫的過程需要注意什么?
答:我們在重寫equals(Object obj)方法,需要遵守JAVA官方的通用約定(詳細(xì)請看示例代碼),約定簡述:
自反性:對于非 null 的對象 x,必須有 x.equals(x)=true;
對稱性:如果 x.equals(y)=true,那么 y.equals(x) 必須也為true;
傳遞性:如果 x.equals(y)=true 而且 y.equals(z)=true,那么x.equals(z) 必須為true;
對于非 null 的對象 x,一定有x.equals(null)=false
當(dāng)
equals(Object obj)方法被重寫時,通常有必要重寫 hashCode 方法,以維護(hù) hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼。
根據(jù)上述約定,我們可以按如下步驟重寫equals(Object obj):
1). 先使用 == 操作符判斷兩個對象的引用地址是否相同。
2). 使用instanceof來判斷 兩個對象的類型是否一致。
3). 如果類型相同,則把待比較參數(shù)轉(zhuǎn)型,逐一比較兩個對象內(nèi)部的值是否一致,全部一致才返回true,否則返回false。
4). 重寫hashCode方法,確保相等的兩個對象必須具有相等的哈希碼。
- 我們在重寫一個類的
hashCode方法時,最好是將所有用于相等性檢查的字段都進(jìn)行hashCode計算,最后將所有hashCode值相加,得出最終的hashCode,這樣可以保證hashCode生成均勻,不容易產(chǎn)生碰撞。
常見數(shù)據(jù)類型hashcode計算方式如下(參考自JDK源碼):
| 重要字段var的類型 | hash運(yùn)算 |
|---|---|
| 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 方法在非空對象引用上實(shí)現(xiàn)相等關(guān)系:
* <ul>
* <li>自反性:對于任何非空引用值 x,x.equals(x) 都應(yīng)返回 true。
*
* <li>對稱性:對于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時,
* x.equals(y) 才應(yīng)返回 true。
*
* <li>傳遞性:對于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,
* 并且 y.equals(z) 返回 true,那么 x.equals(z) 應(yīng)返回 true。
*
* <li>一致性:對于任何非空引用值 x 和 y,多次調(diào)用 x.equals(y) 始終返回
* true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。
*
* <li>對于任何非空引用值 x,x.equals(null) 都應(yīng)返回 false。
* </ul>
*
* <p>
* Object 類的 equals 方法實(shí)現(xiàn)對象上差別可能性最大的相等關(guān)系;
* 即,對于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) x 和 y 引用同一個對象時,
* 此方法才返回 true(x == y 具有值 true)。
*
* <p>
* 注意:當(dāng)此方法被重寫時,通常有必要重寫 hashCode 方法,
* 以維護(hù) hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼。
*
* @param 要與之比較的引用對象。
* @return 如果此對象與 obj 參數(shù)相同,則返回 true;否則返回 false。
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
4.
問:如果需要您去維護(hù)一個類的hash散列表,如何設(shè)計,如何解決hash沖突?
答:我們在設(shè)計類的hash散列表時,不能保證每個元素的hash值都是不一樣的,這樣就會造成hash沖突。解決hash沖突有如下4種方法:
開發(fā)定址法:既然當(dāng)前位置容不下沖突的元素了,那就再找一個空的位置存儲 Hash 沖突的值(當(dāng)前 index 沖突了,那么將沖突的元素放在 index+1)。
再散列法:換一個 Hash 算法再計算一個 hash 值,如果不沖突了就存儲值(例如第一個算法是名字的首字母的 Hash 值,如果沖突了,計算名字的第二個字母的 Hash 值,如果沖突解決了則將值放入數(shù)組中)。
鏈地址法:每個數(shù)組中都存有一個單鏈表,發(fā)生 Hash 沖突時,只是將沖突的 value 當(dāng)作新節(jié)點(diǎn)插入到鏈表(HashMap 解決沖突的辦法)。
公共溢出區(qū)法:將沖突的 value 都存到另外一個順序表中,查找時如果當(dāng)前表沒有對應(yīng)值,則去溢出區(qū)進(jìn)行順序查找。
總結(jié)
- 當(dāng)你真要的需要重寫
equals方法,這兩點(diǎn)一定要記?。?/li>
A.如果兩個對象相等(equals() 返回 true),那么它們的 hashCode()一定要相同;
B.如果兩個對象hashCode()相等,它們并不一定相等(equals() 不一定返回 true)。
如果重寫的
equals方法但不重寫hashCode,都是耍流氓,會有意想不到的結(jié)果。重寫
hashCode方法時,盡可能將所有用于相等比較的參數(shù)都參與hashCode的計算。建立hash散列表的意義就是在于,提高查詢效率,當(dāng)數(shù)據(jù)量大時,尤為顯著。
參考文章:
Java中hashCode的作用
如何正確實(shí)現(xiàn) Java 中的 HashCode
Java 的 equals 與 hashcode 對比分析
程序員必須搞清的概念equals和=和hashcode的區(qū)別
Android 面試準(zhǔn)備之「equals 和 == 」