重寫equals方法的時(shí)候?yàn)槭裁葱枰貙慼ashcode

困擾我很久的問題,一直不明白為什么重寫equals()方法的時(shí)候要重寫hashCode()方法飒筑,這次總算弄明白了绽昏,作此分享全谤,如有不對(duì)之處认然,望大家指正卷员。

一、equals()方法

先說說equals()方法削饵。
??查看Java的Object.equals()方法,如下:

public boolean equals(Object object){
      return(this == obj);
}

可以看到這里直接用'=='來(lái)直接比較未巫,引用《Java編程思想》里的一句話:“關(guān)系操作符生成的是一個(gè)boolean結(jié)果窿撬,它們計(jì)算的是操作數(shù)的值之間的關(guān)系”。那么'=='比較的值到底是什么呢叙凡?
??我們知道Java有8種基本類型:數(shù)值型(byte劈伴、short、int握爷、long跛璧、float、double)新啼、字符型(char)、布爾型(boolean)师抄,對(duì)于這8種基本類型的比較漓柑,變量存儲(chǔ)的就是值,所以比較的就是'值'本身叨吮。如下辆布,值相等就是true,不等就是false茶鉴。

public static void main(String[] args) {  
        int a=3;                                           
        int b=4;
        int c=3;
        System.out.println(a==b);   //false
        System.out.println(a==c);   //true
    }

對(duì)于非基本類型锋玲,也就是常說的引用數(shù)據(jù)類型:類、接口涵叮、數(shù)組惭蹂,由于變量種存儲(chǔ)的是內(nèi)存中的地址,并不是'值'本身割粮,所以真正比較的是該變量存儲(chǔ)的地址盾碗,可想而知,如果聲明的時(shí)候是2個(gè)對(duì)象舀瓢,地址固然不同廷雅。

public static void main(String[] args) {
        String str1 = new String("123");
        String str2 = new String("123");
        System.out.println(str1 == str2);  //false
    }

可以看到,上面這種比較方法,和Object類中的equals()方法的具體實(shí)現(xiàn)相同航缀,之所以為false商架,是因?yàn)橹苯颖容^的是str1和str2指向的地址,也就是說Object中的equals方法是直接比較的地址芥玉,因?yàn)镺bject類是所有類的基類蛇摸,所以調(diào)用新創(chuàng)建的類的equals方法,比較的就是兩個(gè)對(duì)象的地址灿巧。那么就有人要問了赶袄,如果就是想要比較引用類型實(shí)際的值是否相等,該如何比較呢砸烦?
????鐺鐺鐺...... 重點(diǎn)來(lái)了


要解決上面的問題弃鸦,就是今天要說的equals(),具體的比較由各自去重寫幢痘,比較具體的值的大小唬格。我們可以看看上面字符串的比較,如果調(diào)用String的equals方法的結(jié)果颜说。

public static void main(String[] args) {
        String str1 = new String("123");
        String str2 = new String("123");
        System.out.println(str1.equals(str2));  //true
    }

可以看到返回的true购岗,由興趣的同學(xué)可以去看String equals()的源碼。


所以可以通過重寫equals()方法來(lái)判斷對(duì)象的值是否相等门粪,但是有一個(gè)要求:equals()方法實(shí)現(xiàn)了等價(jià)關(guān)系喊积,即:

  • 自反性:對(duì)于任何非空引用x,x.equals(x)應(yīng)該返回true玄妈;
  • 對(duì)稱性:對(duì)于任何引用x和y乾吻,如果x.equals(y)返回true,那么y.equals(x)也應(yīng)該返回true拟蜻;
  • 傳遞性:對(duì)于任何引用x绎签、y和z,如果x.equals(y)返回true酝锅,y.equals(z)返回true诡必,那么x.equals(z)也應(yīng)該返回true;
  • 一致性:如果x和y引用的對(duì)象沒有發(fā)生變化搔扁,那么反復(fù)調(diào)用x.equals(y)應(yīng)該返回同樣的結(jié)果爸舒;
  • 非空性:對(duì)于任意非空引用x,x.equals(null)應(yīng)該返回false稿蹲;

二扭勉、hashCode()方法

此方法返回對(duì)象的哈希碼值,什么是哈希碼场绿?度娘找到的相關(guān)定義:

哈希碼產(chǎn)生的依據(jù):哈希碼并不是完全唯一的剖效,它是一種算法嫉入,讓同一個(gè)類的對(duì)象按照自己不同的特征盡量的有不同的哈希碼焰盗,但不表示不同的對(duì)象哈希碼完全不同璧尸。也有相同的情況,看程序員如何寫哈希碼的算法熬拒。

簡(jiǎn)單理解就是一套算法算出來(lái)的一個(gè)值爷光,且這個(gè)值對(duì)于這個(gè)對(duì)象相對(duì)唯一。哈希算法有一個(gè)協(xié)定:在 Java 應(yīng)用程序執(zhí)行期間澎粟,在對(duì)同一對(duì)象多次調(diào)用 hashCode 方法時(shí)蛀序,必須一致地返回相同的整數(shù),前提是將對(duì)象進(jìn)行hashcode比較時(shí)所用的信息沒有被修改活烙。(ps:要是每次都返回不一樣的徐裸,就沒法玩兒了)

public static void main(String[] args) {
        List<Long> test1 = new ArrayList<Long>();
        test1.add(1L);
        test1.add(2L);
        System.out.println(test1.hashCode());  //994
        test1.set(0,2L);
        System.out.println(test1.hashCode());  //1025
    }

三、標(biāo)題解答

首先來(lái)看一段代碼:

public class HashMapTest {
    private int a;

    public HashMapTest(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        map.put(instance, 1);
        Integer value = map.get(new HashMapTest(1));
        if (value != null) {
            System.out.println(value);
        } else {
            System.out.println("value is null");
        }
    } 

}
//程序運(yùn)行結(jié)果: value is null

簡(jiǎn)單說下HashMap的原理啸盏,HashMap存儲(chǔ)數(shù)據(jù)的時(shí)候重贺,是取的key值的哈希值,然后計(jì)算數(shù)組下標(biāo)回懦,采用鏈地址法解決沖突气笙,然后進(jìn)行存儲(chǔ);取數(shù)據(jù)的時(shí)候怯晕,依然是先要獲取到hash值潜圃,找到數(shù)組下標(biāo),然后for遍歷鏈表集合舟茶,進(jìn)行比較是否有對(duì)應(yīng)的key谭期。比較關(guān)心的有2點(diǎn):1.不管是put還是get的時(shí)候,都需要得到key的哈希值吧凉,去定位key的數(shù)組下標(biāo)隧出; 2.在get的時(shí)候,需要調(diào)用equals方法比較是否有相等的key存儲(chǔ)過客燕。
??反過來(lái)鸳劳,我們?cè)俜治錾厦婺嵌未a,Map的key是我們自己定義的一個(gè)類也搓,可以看到赏廓,我們沒有重寫equal方法,更沒重寫hashCode方法傍妒,意思是map在進(jìn)行存儲(chǔ)的時(shí)候是調(diào)用的Object類中equals()和hashCode()方法幔摸。為了證實(shí),我們打印下hashCode碼颤练。

public class HashMapTest {
    private Integer a;

    public HashMapTest(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        System.out.println("instance.hashcode:" + instance.hashCode());
        map.put(instance, 1);
        HashMapTest newInstance = new HashMapTest(1);
        System.out.println("newInstance.hashcode:" + newInstance.hashCode());
        Integer value = map.get(newInstance);
        if (value != null) {
            System.out.println(value);
        } else {
            System.out.println("value is null");
        }
    }
}
//運(yùn)行結(jié)果:
//instance.hashcode:929338653
//newInstance.hashcode:1259475182
//value is null

不出所料既忆,hashCode不一致,所以對(duì)于為什么拿不到數(shù)據(jù)就很清楚了。這2個(gè)key患雇,在Map計(jì)算的時(shí)候跃脊,可能數(shù)組下標(biāo)就不一致,就算數(shù)據(jù)下標(biāo)碰巧一致苛吱,根據(jù)前面酪术,最后equals比較的時(shí)候也不可能相等(很顯然,這是2個(gè)對(duì)象翠储,在堆上的地址必定不一樣)绘雁。我們繼續(xù)往下看,假如我們重寫了equals方法援所,將這2個(gè)對(duì)象都put進(jìn)去庐舟,根據(jù)map的原理,只要是key一樣住拭,后面的值會(huì)替換前面的值挪略,接下來(lái)我們實(shí)驗(yàn)下:

public class HashMapTest {
    private Integer a;

    public HashMapTest(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        HashMapTest newInstance = new HashMapTest(1);
        map.put(instance, 1);
        map.put(newInstance, 2);
        Integer value = map.get(instance);
        System.out.println("instance value:"+value);
        Integer value1 = map.get(newInstance);
        System.out.println("newInstance value:"+value1);

    }

    public boolean equals(Object o) {
        if(o == this) {
            return true;
        } else if(!(o instanceof HashMapTest)) {
            return false;
        } else {
            HashMapTest other = (HashMapTest)o;
            if(!other.canEqual(this)) {
                return false;
            } else {
                Integer this$data = this.getA();
                Integer other$data = other.getA();
                if(this$data == null) {
                    if(other$data != null) {
                        return false;
                    }
                } else if(!this$data.equals(other$data)) {
                    return false;
                }

                return true;
            }
        }
    }
    protected boolean canEqual(Object other) {
        return other instanceof HashMapTest;
    }

    public void setA(Integer a) {
        this.a = a;
    }

    public Integer getA() {
        return a;
    }
}
//運(yùn)行結(jié)果:
//instance value:1
//newInstance value:2

你會(huì)發(fā)現(xiàn),不對(duì)呀废酷?同樣的一個(gè)對(duì)象瘟檩,為什么在map中存了2份,map的key值不是不能重復(fù)的么澈蟆?沒錯(cuò)墨辛,它就是存的2份,只不過在它看來(lái)趴俘,這2個(gè)的key是不一樣的睹簇,因?yàn)樗麄兊墓4a就是不一樣的,可以自己測(cè)試下寥闪,上面打印的hash碼確實(shí)不一樣太惠。那怎么辦?只有重寫hashCode()方法疲憋,更改后的代碼如下:

public class HashMapTest {
    private Integer a;

    public HashMapTest(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Map<HashMapTest, Integer> map = new HashMap<HashMapTest, Integer>();
        HashMapTest instance = new HashMapTest(1);
        System.out.println("instance.hashcode:" + instance.hashCode());
        HashMapTest newInstance = new HashMapTest(1);
        System.out.println("newInstance.hashcode:" + newInstance.hashCode());
        map.put(instance, 1);
        map.put(newInstance, 2);
        Integer value = map.get(instance);
        System.out.println("instance value:"+value);
        Integer value1 = map.get(newInstance);
        System.out.println("newInstance value:"+value1);

    }

    public boolean equals(Object o) {
        if(o == this) {
            return true;
        } else if(!(o instanceof HashMapTest)) {
            return false;
        } else {
            HashMapTest other = (HashMapTest)o;
            if(!other.canEqual(this)) {
                return false;
            } else {
                Integer this$data = this.getA();
                Integer other$data = other.getA();
                if(this$data == null) {
                    if(other$data != null) {
                        return false;
                    }
                } else if(!this$data.equals(other$data)) {
                    return false;
                }

                return true;
            }
        }
    }
    protected boolean canEqual(Object other) {
        return other instanceof HashMapTest;
    }

    public void setA(Integer a) {
        this.a = a;
    }

    public Integer getA() {
        return a;
    }

    public int hashCode() {
        boolean PRIME = true;
        byte result = 1;
        Integer $data = this.getA();
        int result1 = result * 59 + ($data == null?43:$data.hashCode());
        return result1;
    }
}
//運(yùn)行結(jié)果:
//instance.hashcode:60
//newInstance.hashcode:60
//instance value:2
//newInstance value:2

可以看到凿渊,他們的hash碼是一致的,且最后的結(jié)果也是預(yù)期的缚柳。


完美的分界線

ps.總結(jié):對(duì)于這個(gè)問題埃脏,是比較容易被忽視的,曾經(jīng)同時(shí)趟過這坑秋忙,Map中存了2個(gè)數(shù)值一樣的key彩掐,所以大家謹(jǐn)記喲! 在重寫equals方法的時(shí)候灰追,一定要重寫hashCode方法堵幽。
最后一點(diǎn):有這個(gè)要求的癥結(jié)在于狗超,要考慮到類似HashMap、HashTable朴下、HashSet的這種散列的數(shù)據(jù)類型的運(yùn)用努咐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桐猬,隨后出現(xiàn)的幾起案子麦撵,更是在濱河造成了極大的恐慌刽肠,老刑警劉巖溃肪,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異音五,居然都是意外死亡惫撰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門躺涝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厨钻,“玉大人,你說我怎么就攤上這事坚嗜『话颍” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵苍蔬,是天一觀的道長(zhǎng)诱建。 經(jīng)常有香客問我,道長(zhǎng)碟绑,這世上最難降的妖魔是什么俺猿? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮格仲,結(jié)果婚禮上押袍,老公的妹妹穿的比我還像新娘。我一直安慰自己凯肋,他們只是感情好谊惭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侮东,像睡著了一般圈盔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苗桂,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天药磺,我揣著相機(jī)與錄音,去河邊找鬼煤伟。 笑死癌佩,一個(gè)胖子當(dāng)著我的面吹牛木缝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播围辙,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼我碟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了姚建?” 一聲冷哼從身側(cè)響起矫俺,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掸冤,沒想到半個(gè)月后厘托,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稿湿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年铅匹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饺藤。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡包斑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涕俗,到底是詐尸還是另有隱情罗丰,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布再姑,位于F島的核電站萌抵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏询刹。R本人自食惡果不足惜谜嫉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凹联。 院中可真熱鬧沐兰,春花似錦、人聲如沸蔽挠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)澳淑。三九已至比原,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杠巡,已是汗流浹背量窘。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留氢拥,地道東北人蚌铜。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓锨侯,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冬殃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子囚痴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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