第9條:覆蓋equals時總要覆蓋hashCode

equals方法和hashCode方法均是Object對象的方法。Object中關(guān)于hashCode約定的規(guī)范如下:

  1. 在應(yīng)用程序的執(zhí)行期間,只要對象的equals方法的比較操作所用到的信息沒有被修改苇羡,那么對同一個對象調(diào)用多次,hashCode方法都必須始終如一地返回同一個整數(shù)婿牍。在同一個應(yīng)用程序的多次執(zhí)行過程中哥牍,每次執(zhí)行所返回的整數(shù)可以不一致。
    2.如果兩個對象根據(jù)equals(Object)方法比較是相等的环鲤,那么調(diào)用這兩個對象中任意一個對象的hashCode方法都必須產(chǎn)生同樣的整數(shù)結(jié)果纯趋。
    3.如果兩個對象根據(jù)equals(Object)方法比較是不相等的,那么調(diào)用這兩個對象中任意一個對象的hashCode方法冷离,則不一定要產(chǎn)生不同的整數(shù)結(jié)果吵冒。但是程序員應(yīng)該知道,給不相等的對象產(chǎn)生截然不同的整數(shù)結(jié)果西剥,有可能提高散列表(hash table)的性能痹栖。
1.jpg
2.jpg

如果某個類違反Object的hashCode的通用約定,會導(dǎo)致該類無法結(jié)合所有基于散列的集合一起正常運轉(zhuǎn)瞭空,如:HashMap揪阿、HashSet和HashTable。
覆蓋equals方法而沒有覆蓋hashCode方法違反了約定的第2條:相等的對象必須具有相等的散列碼咆畏。

如下示例:
package com.wuyafu.java.effective.hashcode;

import java.util.HashMap;
import java.util.Map;

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

public PhoneNumber(int areaCode, int prefix, int lineNumber) {
    rangeCheck(areaCode, 999, "area code");
    rangeCheck(prefix, 999, "prefix");
    rangeCheck(lineNumber, 9999, "lineNumber");
    this.areaCode = (short)areaCode;
    this.prefix = (short)prefix;
    this.lineNumber = (short)lineNumber;
}

private static void rangeCheck(int arg, int max, String name){
    if (arg < 0|| arg > max) {
        throw new IllegalArgumentException(name + ":" + arg);
    }
}

@Override
public boolean equals(Object o){
    if (o == this)
        return true;
    if (!(o instanceof PhoneNumber))
        return false;
    PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNumber == lineNumber 
    && pn.prefix == prefix 
    && pn.areaCode == areaCode;
}   
/**
 * @param args
 */
public static void main(String[] args) {
    // TODO Auto-generated method stub
    Map<PhoneNumber, String> m = 
                      new HashMap<PhoneNumber, String>();
    m.put(new PhoneNumber(408, 867, 5309), "Jenny");
    System.out.println(m.get(new PhoneNumber(408, 867, 5309)));
}

}
由于PhoneNumber類沒有覆蓋hashCode方法南捂,從而導(dǎo)致兩個相等的實例具有不相等的散列碼,違反了hashCode的約定旧找。

為解決這個問題溺健,只需為PhoneNumber類提供一個適當?shù)膆ashCode方法即可。
如下:
@Override
public int hashCode(){return 42;}

該方法確保了相等的對象總是具有同樣的散列碼钦讳。但是它也極為惡劣矿瘦,因為它使得每個對象都具有同樣的散列碼枕面。因此每個對象都被映射到同一個散列桶中,使散列表退化為鏈表缚去。
一個好的散列函數(shù)通常傾向于“為不相等的對象產(chǎn)生不相等的散列碼”潮秘,散列函數(shù)應(yīng)該把集合中不相等的實例均勻地分布到所有可能的散列值上。實現(xiàn)這種理想狀態(tài)很難易结,但如下方法可以接近理想狀態(tài):

3.jpg

在散列碼的計算過程中枕荞,可以把冗余域排除在外。
在公式中result不能為0,17為任意選的值搞动。
31為奇素數(shù)躏精,有更好的性能

利用上述接近辦法,修改PhoneNumber類的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)部,而不是每次請求的時候都重新計算散列碼箩溃〔t吃?梢酝ㄟ^“延遲初始化”散列碼的方式來實現(xiàn),修改PhoneNumber類的hashCode方法如下:
private volatile int hashCode;
@Override
public int hashCode(){
int result = hashCode;
if (result == 0) {
result = 17;
result = 31* result + areaCode;
result = 31* result + prefix;
result = 31* result + lineNumber;
}
}

總結(jié):當覆蓋equals方法時涣旨,要覆蓋hashCode方法歪架,并且采用公式來將對象的關(guān)鍵域參與到散列碼的計算中,確保不相同的對象在不同的散列桶中霹陡。若該類是不可變的和蚪,可以考慮使用“延遲初始化”散列碼的方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末烹棉,一起剝皮案震驚了整個濱河市攒霹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浆洗,老刑警劉巖剔蹋,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辅髓,居然都是意外死亡,警方通過查閱死者的電腦和手機少梁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門洛口,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人凯沪,你說我怎么就攤上這事第焰。” “怎么了妨马?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵挺举,是天一觀的道長杀赢。 經(jīng)常有香客問我,道長湘纵,這世上最難降的妖魔是什么脂崔? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮梧喷,結(jié)果婚禮上砌左,老公的妹妹穿的比我還像新娘。我一直安慰自己铺敌,他們只是感情好汇歹,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著偿凭,像睡著了一般产弹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弯囊,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天痰哨,我揣著相機與錄音,去河邊找鬼常挚。 笑死作谭,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奄毡。 我是一名探鬼主播折欠,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吼过!你這毒婦竟也來了锐秦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤盗忱,失蹤者是張志新(化名)和其女友劉穎酱床,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趟佃,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡扇谣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闲昭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐寨。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖序矩,靈堂內(nèi)的尸體忽然破棺而出鸯绿,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布瓶蝴,位于F島的核電站毒返,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舷手。R本人自食惡果不足惜拧簸,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聚霜。 院中可真熱鬧狡恬,春花似錦、人聲如沸蝎宇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姥芥。三九已至兔乞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凉唐,已是汗流浹背庸追。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留台囱,地道東北人淡溯。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像簿训,于是被迫代替她去往敵國和親咱娶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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