覆蓋equals時總要覆蓋hashCode

1. 什么是hashcode方法?
  • hashcode方法返回對象的哈希碼值
  • 在應(yīng)用程序的執(zhí)行期間瑟俭,只要對象的equals方法的比較操作所用到的信息沒有改變,那么對于這同一個對象調(diào)用多次,hashcode方法都必須返回同一個整數(shù)玉掸。
  • hashcode的存在主要用于查找的快捷性,如Hashtable,HashMap等醒叁,hashcode是用來在散列存儲結(jié)構(gòu)中確定對象的存儲地址的司浪。
2. hashcode相等與對象相等之間的關(guān)系:(保證設(shè)計是規(guī)范的前提下)
  • 如果兩個對象相同榆俺,那么兩個對象的hashcode也必須相同
  • 如果兩個對象的hashcode相同殊校,并不一定表示兩個對象就相同呼畸,也就是不一定適合equals方法编检,只能夠說明兩個對象在散列表存儲結(jié)構(gòu)中苍碟,“存放在同一個籃子里”现恼。
3. 為什么覆蓋equals方法時總要覆蓋hashcode方法偷厦?

因?yàn)槿绻桓采wequals方法的話展氓,相等的對象可能返回的不相同的hash code捆愁。

例子:我們創(chuàng)建兩個相同值的對象割去,然后將p1對象作為key,"jenny"插入到hashmap中,再將p2對象作為key,獲取對應(yīng)的value值

public class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = (short) areaCode;
        this.prefix = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }
    //覆蓋equals方法
    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (!(obj instanceof PhoneNumber))
            return false;
        //必須滿足如下條件昼丑,才能說明為同一個對象
        PhoneNumber pn = (PhoneNumber) obj;
        return pn.areaCode == areaCode && pn.prefix == prefix && pn.lineNumber == lineNumber;
    }

    public static void main(String[] args){
        Map<PhoneNumber, String> m = new HashMap<>();
        //創(chuàng)建兩個相同的對象
        PhoneNumber p1 = new PhoneNumber(707, 867, 5309);
        PhoneNumber p2 = new PhoneNumber(707, 867, 5309);
        //添加到hashmap中
        m.put(p1, "Jenny");
        //比較對象p1和p2
        System.out.println("p1.equals(p2): " + p1.equals(p2));
        System.out.println("p2.equals(p1): " + p2.equals(p1));
        //從hashmap中去獲取對象p1和p2
        System.out.println("get p1 from hashmap: " + m.get(p1));
        System.out.println("get p2 from hashmap: " + m.get(p2));
    }
}
輸出結(jié)果:
p1.equals(p2): true
p2.equals(p1): true
get p1 from hashmap: Jenny
get p2 from hashmap: null

前兩個結(jié)果說明p1和p2為同一對象呻逆,但我們發(fā)現(xiàn)以p1作為key時,是可以獲取到對應(yīng)的value菩帝,而以p2作為key時咖城,卻獲取到null憔足。

實(shí)際上,是因?yàn)镻honeNumber類沒有覆蓋hashcode方法酒繁,從而導(dǎo)致兩個相等的實(shí)例具有不相等的散列碼滓彰。

//輸出p1和p2的hashcode
System.out.println("p1's hashcode: " + p1.hashCode());
System.out.println("p2's hashcode: " + p2.hashCode());
輸出結(jié)果:
p1's hashcode: 460141958
p2's hashcode: 1163157884

因此,導(dǎo)致put方法時把電話存放在一個散列桶中州袒,而get方法卻在另個散列桶中查找這個電話號碼揭绑。即使這兩個實(shí)例正好放到同一個散列桶中(發(fā)生“沖突”的情況),get方法也必定會返回null郎哭,因?yàn)閔ashmap有一項(xiàng)優(yōu)化他匪,可以將與每個相關(guān)聯(lián)的散列碼緩存起來,如果散列碼不匹配夸研,那么將會返回null邦蜜。

4. 如何在覆蓋equals方法時覆蓋hashcode方法?

實(shí)際上亥至,問題很簡單悼沈,只要我們重寫hashcode方法,返回一個適當(dāng)?shù)膆ash code即可姐扮。

@Override
public int hashCode() {
    return 42;
}

這樣的確能解決上面的問題絮供,但實(shí)際上,這么做茶敏,會導(dǎo)致很差的性能壤靶,因?yàn)樗偸谴_保每個對象都具有同樣的散列碼。因此惊搏,每個對象都被映射到同一個散列桶中贮乳,使得散列表退化成鏈表。

** 一個好的散列函數(shù)通常傾向于“為不相等的對象產(chǎn)生不同的散列碼”**恬惯。
理想情況下向拆,散列函數(shù)應(yīng)該把集合中不相等的實(shí)例均勻地分布到所有可能的散列值上。但實(shí)際上宿崭,要達(dá)到這種理想的情形是非常困難的亲铡。

5. 如何設(shè)置一個好的散列函數(shù)才写?

a. 為對象計算int類型的散列碼c:

  • 對于boolean類型葡兑,計算(f?1:0)
  • 對于byte,char,short,int類型,則計算(int)f
  • 對于long類型赞草,計算(int)(f^(f>>>32))
  • 對于float類型讹堤,計算Float.floatToIntBits(f)
  • 對于Double類型,計算Double.doubleToLongBits(f)厨疙,然后再按照long類型處理
  • 對于對象引用洲守,并且該類的equals方法通過遞歸地調(diào)用equals的方式來比較這個域,則同樣為這個域遞歸地調(diào)用hashcode
  • 對于數(shù)組,則把每一個元素當(dāng)作單獨(dú)的域來處理梗醇。

b. 將獲取到的c合并:result = 31 * reuslt + c;

c. 返回result

比如知允,我們可以優(yōu)化上面的hashcode方法:

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

如果一個類是不可變類,并且計算散列碼的開銷也比較大叙谨,就應(yīng)該考慮吧散列碼緩存在對象內(nèi)部温鸽,而不是每次請求的時候都重新計算散列碼。

 private volatile static int hashcode;
    
    @Override
    public int hashCode() {
        int result = hashcode;
        if (result == 0){
            result = 31 * result + areaCode;
            result = 31 * result + prefix;
            result = 31 * result + lineNumber;
            hashcode = result;
        }
        return result;
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末手负,一起剝皮案震驚了整個濱河市涤垫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌竟终,老刑警劉巖蝠猬,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異统捶,居然都是意外死亡榆芦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門喘鸟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歧杏,“玉大人,你說我怎么就攤上這事迷守∪蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵兑凿,是天一觀的道長凯力。 經(jīng)常有香客問我,道長礼华,這世上最難降的妖魔是什么咐鹤? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮圣絮,結(jié)果婚禮上祈惶,老公的妹妹穿的比我還像新娘。我一直安慰自己扮匠,他們只是感情好捧请,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著棒搜,像睡著了一般疹蛉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上力麸,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天可款,我揣著相機(jī)與錄音育韩,去河邊找鬼。 笑死闺鲸,一個胖子當(dāng)著我的面吹牛筋讨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播摸恍,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼版仔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了误墓?” 一聲冷哼從身側(cè)響起蛮粮,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谜慌,沒想到半個月后然想,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡欣范,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年变泄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恼琼。...
    茶點(diǎn)故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡妨蛹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晴竞,到底是詐尸還是另有隱情蛙卤,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布噩死,位于F島的核電站颤难,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏已维。R本人自食惡果不足惜行嗤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垛耳。 院中可真熱鬧栅屏,春花似錦、人聲如沸堂鲜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泡嘴。三九已至甫恩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酌予,已是汗流浹背磺箕。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抛虫,地道東北人松靡。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像建椰,于是被迫代替她去往敵國和親雕欺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評論 2 354

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