JAVA基礎(chǔ)-自問自答學(xué)hashCode和equals

前言

hashCodeequals常常在面試中會(huì)被問到统屈,在工作中我們也有可能遇到要重寫對(duì)象equals方法的情況,而且hashCode方法的設(shè)計(jì)思想值得我們學(xué)習(xí)牙躺,所以我們有必要去深入學(xué)習(xí)一下這兩個(gè)方法愁憔。

下面我就以面試問答的形式學(xué)習(xí)我們的——hashcodeequals方法(源碼分析基于JDK8)

問答內(nèi)容

1.

問:hashCode方法有了解過嗎?這個(gè)方法有什么用孽拷?

答:從JAVA官方對(duì)hashCode方法的說明定義(定義在示例代碼中)吨掌,我們可以得知hashCode的作用有如下幾點(diǎn):

  1. hashCode的存在主要用于查找的快捷性,如Hashtable乓搬,HashMap等思犁,hashCode是用來在散列存儲(chǔ)結(jié)構(gòu)中確定對(duì)象的存儲(chǔ)地址的。

  2. 如果兩個(gè)對(duì)象相同进肯,就是適用于equals(java.lang.Object) 方法激蹲,那么這兩個(gè)對(duì)象的hashCode一定要相同。

  3. 如果對(duì)象的equals方法被重寫江掩,那么對(duì)象的hashCode也盡量重寫学辱,并且產(chǎn)生hashCode使用的對(duì)象乘瓤,一定要和equals方法中使用的一致,否則就會(huì)違反上面提到的第2點(diǎn)策泣。

  4. 兩個(gè)對(duì)象的hashCode相同衙傀,并不一定表示兩個(gè)對(duì)象就相同,也就是不一定適用于equals(java.lang.Object) 方法萨咕,只能夠說明這兩個(gè)對(duì)象在散列存儲(chǔ)結(jié)構(gòu)中统抬,如Hashtable,他們“存放在同一個(gè)籃子里”危队。

1.hashcode是用來查找的聪建,如果你學(xué)過數(shù)據(jù)結(jié)構(gòu)就應(yīng)該知道,在查找和排序這一章有  
例如內(nèi)存中有這樣的位置  
0  1  2  3  4  5  6  7    
而我有個(gè)類茫陆,這個(gè)類有個(gè)字段叫ID,我要把這個(gè)類存放在以上8個(gè)位置之一金麸,
如果不用hashcode而任意存放,那么當(dāng)查找時(shí)就需要到這八個(gè)位置里挨個(gè)去找簿盅,或者用二分法一類的算法挥下。  
但如果用hashcode那就會(huì)使效率提高很多。  
我們這個(gè)類中有個(gè)字段叫ID,那么我們就定義我們的hashcode為ID%8桨醋,
然后把我們的類存放在取得得余數(shù)那個(gè)位置棚瘟。比如我們的ID為9,
9除8的余數(shù)為1讨盒,那么我們就把該類存在1這個(gè)位置解取,
如果ID是13,求得的余數(shù)是5返顺,那么我們就把該類放在5這個(gè)位置。
這樣蔓肯,以后在查找該類時(shí)就可以通過ID除 8求余數(shù)直接找到存放的位置了遂鹊。  
  
2.但是如果兩個(gè)類有相同的hashcode怎么辦那(我們假設(shè)上面的類的ID不是唯一的),
例如9除以8和17除以8的余數(shù)都是1蔗包,那么這是不是合法的秉扑,
回答是:可以這樣。那么如何判斷呢调限?在這個(gè)時(shí)候就需要定義 equals了舟陆。  
也就是說,我們先通過 hashcode來判斷兩個(gè)類是否存放某個(gè)桶里耻矮,
但這個(gè)桶里可能有很多類秦躯,那么我們就需要再通過 equals 來在這個(gè)桶里找到我們要的類。  
那么裆装。重寫了equals()踱承,為什么還要重寫hashCode()呢倡缠?  
想想,你要在一個(gè)桶里找東西茎活,你必須先要找到這個(gè)桶啊昙沦,
你不通過重寫hashcode()來找到桶,光重寫equals()有什么用啊  

上述回答轉(zhuǎn)載于:Java中hashCode的作用 由于作者總結(jié)的太好载荔,所以直接轉(zhuǎn)載了

示例代碼:

package java.lang;

public class Object {
·······

    /**
     * 返回該對(duì)象的哈希碼值盾饮。
     * 支持此方法是為了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能
     * {@link java.util.HashMap}.
     * <p>
     * hashCode 的常規(guī)協(xié)定是:
     * <ul>
     * <li>在 Java 應(yīng)用程序執(zhí)行期間,在對(duì)同一對(duì)象多次調(diào)用 hashCode 方法時(shí)懒熙,
     *     必須一致地返回相同的整數(shù)丐谋,前提是將對(duì)象進(jìn)行 equals 比較時(shí)所用的信息沒有被修改。
     *     從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行煌珊,該整數(shù)無需保持一致号俐。
     * <li>如果根據(jù) equals(Object) 方法,兩個(gè)對(duì)象是相等的定庵,
     *     那么對(duì)這兩個(gè)對(duì)象中的每個(gè)對(duì)象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果吏饿。
     * <li>如果根據(jù) equals(java.lang.Object) 方法,兩個(gè)對(duì)象不相等蔬浙,
     *     那么對(duì)這兩個(gè)對(duì)象中的任一對(duì)象上調(diào)用 hashCode 方法不 要求一定生成不同的整數(shù)結(jié)果猪落。
     *     但是,程序員應(yīng)該意識(shí)到畴博,為不相等的對(duì)象生成不同整數(shù)結(jié)果可以提高哈希表的性能笨忌。
     * </ul>
     * <p>
     * 實(shí)際上,由 Object 類定義的 hashCode 方法確實(shí)會(huì)針對(duì)不同的對(duì)象返回不同的整數(shù)俱病。
     * (這一般是通過將該對(duì)象的內(nèi)部地址轉(zhuǎn)換成一個(gè)整數(shù)來實(shí)現(xiàn)的官疲,
     * 但是 JavaTM 編程語言不需要這種實(shí)現(xiàn)技巧。)
     *
     * @return  此對(duì)象的一個(gè)哈希碼值亮隙。
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.lang.System#identityHashCode
     */
    public native int hashCode();
·······
}

2.

問:談?wù)勀銓?duì)equals(Object obj)方法的理解途凫,它和 == 操作符相比,有什么區(qū)別溢吻?

答:
A.== 操作符分為兩種情況:

  • 比較基礎(chǔ)類型(byte,short,int,long,float,double,char,boolean)時(shí)维费,比較的是值是否相等

  • 比較對(duì)象,比較的是對(duì)象在內(nèi)存中的空間地址是否相等促王。

B.equals(Object obj)方法比較也分為兩種情況:

  • 如果一個(gè)類沒有重寫equals(Object obj)方法犀盟,則等價(jià)于通過==比較兩個(gè)對(duì)象,即比較的是對(duì)象在內(nèi)存中的空間地址是否相等蝇狼。

  • 如果重寫了equals(Object obj)方法阅畴,則根據(jù)重寫的方法內(nèi)容去比較相等,返回true則相等题翰,false則不相等恶阴。

3.

問:那如果要您去重寫equals(Object obj)方法诈胜,您會(huì)怎么做?重寫的過程需要注意什么冯事?

答:我們?cè)谥貙?code>equals(Object obj)方法焦匈,需要遵守JAVA官方的通用約定(詳細(xì)請(qǐng)看示例代碼),約定簡(jiǎn)述:

  • 自反性:對(duì)于非 null 的對(duì)象 x昵仅,必須有 x.equals(x)=true缓熟;

  • 對(duì)稱性:如果 x.equals(y)=true,那么 y.equals(x) 必須也為true摔笤;

  • 傳遞性:如果 x.equals(y)=true 而且 y.equals(z)=true够滑,那么x.equals(z) 必須為true;

  • 對(duì)于非 null 的對(duì)象 x吕世,一定有x.equals(null)=false

  • 當(dāng)equals(Object obj)方法被重寫時(shí)彰触,通常有必要重寫 hashCode 方法,以維護(hù) hashCode 方法的常規(guī)協(xié)定命辖,該協(xié)定聲明相等對(duì)象必須具有相等的哈希碼况毅。

根據(jù)上述約定,我們可以按如下步驟重寫equals(Object obj)

1). 先使用 == 操作符判斷兩個(gè)對(duì)象的引用地址是否相同尔艇。
2). 使用instanceof來判斷 兩個(gè)對(duì)象的類型是否一致尔许。
3). 如果類型相同,則把待比較參數(shù)轉(zhuǎn)型终娃,逐一比較兩個(gè)對(duì)象內(nèi)部的值是否一致味廊,全部一致才返回true,否則返回false
4). 重寫hashCode方法棠耕,確保相等的兩個(gè)對(duì)象必須具有相等的哈希碼余佛。

  • 我們?cè)谥貙懸粋€(gè)類的hashCode方法時(shí),最好是將所有用于相等性檢查的字段都進(jìn)行hashCode計(jì)算昧辽,最后將所有hashCode值相加衙熔,得出最終的hashCode,這樣可以保證hashCode生成均勻搅荞,不容易產(chǎn)生碰撞。

常見數(shù)據(jù)類型hashcode計(jì)算方式如下(參考自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())
hashCode計(jì)算-圖片來自于《Effective Java》

示例代碼:


    /**
     * 指示其他某個(gè)對(duì)象是否與此對(duì)象“相等”框咙。
     * <p>
     * equals 方法在非空對(duì)象引用上實(shí)現(xiàn)相等關(guān)系:
     * <ul>
     * <li>自反性:對(duì)于任何非空引用值 x咕痛,x.equals(x) 都應(yīng)返回 true。
     *
     * <li>對(duì)稱性:對(duì)于任何非空引用值 x 和 y喇嘱,當(dāng)且僅當(dāng) y.equals(x) 返回 true 時(shí)茉贡,
     * x.equals(y) 才應(yīng)返回 true。
     *
     * <li>傳遞性:對(duì)于任何非空引用值 x者铜、y 和 z腔丧,如果 x.equals(y) 返回 true放椰,
     * 并且 y.equals(z) 返回 true,那么 x.equals(z) 應(yīng)返回 true愉粤。
     *
     * <li>一致性:對(duì)于任何非空引用值 x 和 y砾医,多次調(diào)用 x.equals(y) 始終返回 
     *  true 或始終返回 false,前提是對(duì)象上 equals 比較中所用的信息沒有被修改衣厘。
     * 
     * <li>對(duì)于任何非空引用值 x如蚜,x.equals(null) 都應(yīng)返回 false。
     * </ul>
     * 
     * <p>
     * Object 類的 equals 方法實(shí)現(xiàn)對(duì)象上差別可能性最大的相等關(guān)系影暴;
     * 即错邦,對(duì)于任何非空引用值 x 和 y,當(dāng)且僅當(dāng) x 和 y 引用同一個(gè)對(duì)象時(shí)型宙,
     * 此方法才返回 true(x == y 具有值 true)撬呢。
     * 
     * <p>
     * 注意:當(dāng)此方法被重寫時(shí),通常有必要重寫 hashCode 方法妆兑,
     * 以維護(hù) hashCode 方法的常規(guī)協(xié)定魂拦,該協(xié)定聲明相等對(duì)象必須具有相等的哈希碼。
     *
     * @param   要與之比較的引用對(duì)象箭跳。
     * @return  如果此對(duì)象與 obj 參數(shù)相同晨另,則返回 true;否則返回 false谱姓。
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

4.

問:如果需要您去維護(hù)一個(gè)類的hash散列表借尿,如何設(shè)計(jì),如何解決hash沖突屉来?

答:我們?cè)谠O(shè)計(jì)類的hash散列表時(shí)路翻,不能保證每個(gè)元素的hash值都是不一樣的,這樣就會(huì)造成hash沖突茄靠。解決hash沖突有如下4種方法:

  • 開發(fā)定址法:既然當(dāng)前位置容不下沖突的元素了茂契,那就再找一個(gè)空的位置存儲(chǔ) Hash 沖突的值(當(dāng)前 index 沖突了,那么將沖突的元素放在 index+1)慨绳。

  • 再散列法:換一個(gè) Hash 算法再計(jì)算一個(gè) hash 值掉冶,如果不沖突了就存儲(chǔ)值(例如第一個(gè)算法是名字的首字母的 Hash 值,如果沖突了脐雪,計(jì)算名字的第二個(gè)字母的 Hash 值厌小,如果沖突解決了則將值放入數(shù)組中)。

  • 鏈地址法:每個(gè)數(shù)組中都存有一個(gè)單鏈表战秋,發(fā)生 Hash 沖突時(shí)璧亚,只是將沖突的 value 當(dāng)作新節(jié)點(diǎn)插入到鏈表(HashMap 解決沖突的辦法)。

  • 公共溢出區(qū)法:將沖突的 value 都存到另外一個(gè)順序表中脂信,查找時(shí)如果當(dāng)前表沒有對(duì)應(yīng)值癣蟋,則去溢出區(qū)進(jìn)行順序查找透硝。

總結(jié)

  1. 當(dāng)你真要的需要重寫equals方法,這兩點(diǎn)一定要記追杞痢:
  • A.如果兩個(gè)對(duì)象相等(equals() 返回 true)濒生,那么它們的 hashCode()一定要相同;

  • B.如果兩個(gè)對(duì)象hashCode()相等秉撇,它們并不一定相等(equals() 不一定返回 true)甜攀。

  1. 如果重寫的equals方法但不重寫hashCode,都是耍流氓琐馆,會(huì)有意想不到的結(jié)果规阀。

  2. 重寫hashCode方法時(shí),盡可能將所有用于相等比較的參數(shù)都參與hashCode的計(jì)算瘦麸。

  3. 建立hash散列表的意義就是在于谁撼,提高查詢效率,當(dāng)數(shù)據(jù)量大時(shí)滋饲,尤為顯著厉碟。

參考文章:
Java中hashCode的作用
如何正確實(shí)現(xiàn) Java 中的 HashCode
Java 的 equals 與 hashcode 對(duì)比分析
程序員必須搞清的概念equals和=和hashcode的區(qū)別
Android 面試準(zhǔn)備之「equals 和 == 」

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屠缭,隨后出現(xiàn)的幾起案子箍鼓,更是在濱河造成了極大的恐慌,老刑警劉巖呵曹,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件款咖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡奄喂,警方通過查閱死者的電腦和手機(jī)铐殃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跨新,“玉大人富腊,你說我怎么就攤上這事∮蛘剩” “怎么了赘被?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肖揣。 經(jīng)常有香客問我帘腹,道長(zhǎng),這世上最難降的妖魔是什么许饿? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮舵盈,結(jié)果婚禮上陋率,老公的妹妹穿的比我還像新娘球化。我一直安慰自己,他們只是感情好瓦糟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布筒愚。 她就那樣靜靜地躺著,像睡著了一般菩浙。 火紅的嫁衣襯著肌膚如雪巢掺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天劲蜻,我揣著相機(jī)與錄音陆淀,去河邊找鬼。 笑死先嬉,一個(gè)胖子當(dāng)著我的面吹牛轧苫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疫蔓,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼含懊,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了衅胀?” 一聲冷哼從身側(cè)響起岔乔,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滚躯,沒想到半個(gè)月后雏门,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哀九,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年剿配,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阅束。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呼胚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出息裸,到底是詐尸還是另有隱情蝇更,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布呼盆,位于F島的核電站年扩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏访圃。R本人自食惡果不足惜厨幻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧况脆,春花似錦饭宾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盛末,卻和暖如春弹惦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悄但。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工棠隐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人算墨。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓宵荒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親净嘀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子报咳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法挖藏,內(nèi)部類的語法暑刃,繼承相關(guān)的語法,異常的語法膜眠,線程的語...
    子非魚_t_閱讀 31,622評(píng)論 18 399
  • 前言 Java的基類Object提供了一些方法岩臣,其中equals()方法用于判斷兩個(gè)對(duì)象是否相等,hashCode...
    余平的余_余平的平閱讀 2,662評(píng)論 0 5
  • 從三月份找實(shí)習(xí)到現(xiàn)在宵膨,面了一些公司架谎,掛了不少,但最終還是拿到小米辟躏、百度谷扣、阿里、京東捎琐、新浪会涎、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,239評(píng)論 11 349
  • 大象文藝鎮(zhèn)閱讀 437評(píng)論 1 2
  • 我們開始成長(zhǎng)瑞凑,開始學(xué)會(huì)失去末秃,開始做一些自己都無法理解的事。自己卻依舊那么的執(zhí)著籽御,即使失敗了又失敗了练慕,但每次過后都有...
    為應(yīng)閱讀 131評(píng)論 1 2