equals和hashcode區(qū)別股缸?

equals()和hashCode()區(qū)別?


equals():反映的是對象或變量具體的值众弓,即兩個對象里面包含的值--可能是對象的引用恩溅,也可能是值類型的值。

hashCode():計算出對象實例的哈希碼谓娃,并返回哈希碼脚乡,又稱為散列函數(shù)。根類Object的hashCode()方法的計算依賴于對象實例的D(內(nèi)存地址)滨达,故每個Object對象的hashCode都是唯一的奶稠;當然,當對象所對應的類重寫了hashCode()方法時捡遍,結果就截然不同了锌订。

之所以有hashCode方法,是因為在批量的對象比較中画株,hashCode要比equals來得快辆飘,很多集合都用到了hashCode啦辐,比如HashTable。

兩個obj蜈项,如果equals()相等芹关,hashCode()一定相等。
  兩個obj紧卒,如果hashCode()相等侥衬,equals()不一定相等(Hash散列值有沖突的情況,雖然概率很低)跑芳。
所以:
  可以考慮在集合中轴总,判斷兩個對象是否相等的規(guī)則是:
    第一步,如果hashCode()相等博个,則查看第二步怀樟,否則不相等;
    第二步,查看equals()是否相等盆佣,如果相等漂佩,則兩obj相等,否則還是不相等罪塔。

1投蝉、首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。

equals()是對兩個對象的地址值進行的比較(即比較引用是否相同)征堪。

hashCode()是一個本地方法瘩缆,它的實現(xiàn)是根據(jù)本地機器相關的。

2佃蚜、Java語言對equals()的要求如下庸娱,這些要求是必須遵循的:

A 對稱性:如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是“true”谐算。

B 反射性:x.equals(x)必須返回是“true”熟尉。

C 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”洲脂,那么z.equals(x)也應該返回是“true”斤儿。

D 一致性:如果x.equals(y)返回是“true”,只要x和y內(nèi)容一直不變恐锦,不管你重復x.equals(y)多少次往果,返回都是“true”。

任何情況下一铅,x.equals(null)陕贮,永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”潘飘。

3肮之、equals()相等的兩個對象掉缺,hashcode()一定相等;

反過來:hashcode()不等戈擒,一定能推出equals()也不等攀圈;

hashcode()相等,equals()可能相等峦甩,也可能不等。

為什么選擇hashcode方法现喳?


以java.lang.Object來理解,JVM每new一個Object,它都會將這個Object丟到一個Hash哈希表中去,這樣的話,下次做Object的比較或者取這個對象的時候,它會根據(jù)對象的hashcode再從Hash表中取這個對象凯傲。這樣做的目的是提高取對象的效率。具體過程是這樣:

  1. new Object(),JVM根據(jù)這個對象的Hashcode值,放入到對應的Hash表對應的Key上,如果不同的對象確產(chǎn)生了相同的hash值,也就是發(fā)生了Hash key相同導致沖突的情況,那么就在這個Hash key的地方產(chǎn)生一個鏈表,將所有產(chǎn)生相同hashcode的對象放到這個單鏈表上去,串在一起嗦篱。

  2. 比較兩個對象的時候,首先根據(jù)他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,那么就是說他們這兩個對象放在Hash表中的同一個key上,那么他們一定在這個key上的鏈表上冰单。那么此時就只能根據(jù)Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不同的話灸促,肯定他們不能equal.

可能經(jīng)過上面理論的講一下大家都迷糊了诫欠,我也看了之后也是似懂非懂的。下面我舉個例子詳細說明下浴栽。

list是可以重復的荒叼,set是不可以重復的。那么set存儲數(shù)據(jù)的時候是怎樣判斷存進的數(shù)據(jù)是否已經(jīng)存在典鸡。使用equals()方法呢被廓,還是hashcode()方法。

假如用equals()萝玷,那么存儲一個元素就要跟已存在的所有元素比較一遍嫁乘,比如已存入100個元素,那么存101個元素的時候球碉,就要調(diào)用equals方法100次蜓斧。

但如果用hashcode()方法的話,他就利用了hash算法來存儲數(shù)據(jù)的睁冬。

這樣的話每存一個數(shù)據(jù)就調(diào)用一次hashcode()方法挎春,得到一個hashcode值及存入的位置。如果該位置不存在數(shù)據(jù)那么就直接存入豆拨,否則調(diào)用一次equals()方法搂蜓,不相同則存,相同不存辽装。這樣下來整個存儲下來不需要調(diào)用幾次equals方法帮碰,雖然多了幾次hashcode方法,但相對于前面來講效率高了不少拾积。

為什么要重寫equals方法殉挽?


因為Object的equal方法默認是兩個對象的引用的比較丰涉,意思就是指向同一內(nèi)存,地址則相等,否則不相等斯碌;如果你現(xiàn)在需要利用對象里面的值來判斷是否相等一死,則重載equal方法。

說道這個地方我相信很多人會有疑問傻唾,相信大家都被String對象的equals()方法和"=="糾結過一段時間投慈,當時我們知道String對象中equals方法是判斷值的,而==是地址判斷冠骄。

那照這么說equals怎么會是地址的比較呢伪煤?

那是因為實際上JDK中,String凛辣、Math等封裝類都對Object中的equals()方法進行了重寫抱既。

我們先看看Object中equals方法的源碼:

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

我們都知道所有的對象都擁有標識(內(nèi)存地址)和狀態(tài)(數(shù)據(jù)),同時“==”比較兩個對象的的內(nèi)存地址扁誓,所以說使用Object的equals()方法是比較兩個對象的內(nèi)存地址是否相等防泵,即若object1.equals(object2)為true,則表示equals1和equals2實際上是引用同一個對象蝗敢。雖然有時候Object的equals()方法可以滿足我們一些基本的要求捷泞,但是我們必須要清楚我們很大部分時間都是進行兩個對象的比較,這個時候Object的equals()方法就不可以了寿谴,所以才會有String這些類對equals方法的改寫肚邢,依次類推Double、Integer拭卿、Math骡湖。。峻厚。响蕴。等等這些類都是重寫了equals()方法的,從而進行的是內(nèi)容的比較惠桃。希望大家不要搞混了浦夷。

改寫equals時總是要改寫hashcode


java.lnag.Object中對hashCode的約定:

  1. 在一個應用程序執(zhí)行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話辜王,則對該對象調(diào)用hashCode方法多次劈狐,它必須始終如一地返回同一個整數(shù)。
  2. 如果兩個對象根據(jù)equals(Object o)方法是相等的呐馆,則調(diào)用這兩個對象中任一對象的hashCode方法必須產(chǎn)生相同的整數(shù)結果肥缔。
  3. 如果兩個對象根據(jù)equals(Object o)方法是不相等的,則調(diào)用這兩個對象中任一個對象的hashCode方法汹来,不要求產(chǎn)生不同的整數(shù)結果续膳。但如果能不同改艇,則可能提高散列表的性能。

根據(jù)上一個問題坟岔,實際上我們已經(jīng)能很簡單的解釋這一點了谒兄,比如改寫String中的equals為基于內(nèi)容上的比較而不是內(nèi)存地址的話,那么雖然equals相等社付,但并不代表內(nèi)存地址相等承疲,由hashcode方法的定義可知內(nèi)存地址不同,沒改寫的hashcode值也可能不同鸥咖。所以違背了第二條約定燕鸽。

又如new一個對象,再new一個內(nèi)容相等的對象扛或,調(diào)用equals方法返回的true,但他們的hashcode值不同碘饼,將兩個對象存入HashSet中熙兔,會使得其中包含兩個相等的對象,因為是先檢索hashcode值艾恼,不等的情況下才會去比較equals方法的住涉。

hashCode方法使用介紹


Hash表數(shù)據(jù)結構常識:
  一、哈希表基于數(shù)組钠绍。
  二舆声、缺點:基于數(shù)組的,數(shù)組創(chuàng)建后難以擴展柳爽。某些哈希表被基本填滿時媳握,性能下降得非常嚴重。
  三磷脯、沒有一種簡便得方法可以以任何一種順序遍歷表中數(shù)據(jù)項蛾找。
  四、如果不需要有序遍歷數(shù)據(jù)赵誓,并且可以提前預測數(shù)據(jù)量的大小打毛,那么哈希表在速度和易用性方面是無與倫比的。

一俩功、為什么HashCode對于對象是如此的重要:
  一個對象的HashCode就是一個簡單的Hash算法的實現(xiàn)幻枉,雖然它和那些真正的復雜的Hash算法相比還不能叫真正的算法,它如何實現(xiàn)它诡蜓,不僅僅是程序員的編程水平問題熬甫,
  而是關系到你的對象在存取是性能的非常重要的關系.有可能,不同的HashCode可能會使你的對象存取產(chǎn)生蔓罚,成百上千倍的性能差別.
  先來看一下罗珍,在JAVA中兩個重要的數(shù)據(jù)結構:HashMap和Hashtable洽腺,雖然它們有很大的區(qū)別,如繼承關系不同覆旱,對value的約束條件(是否允許null)不同蘸朋,以及線程安全性等有著特定的區(qū)別,但從實現(xiàn)原理上來說扣唱,它們是一致的.所以藕坯,我們只以Hashtable來說明:
  在java中,存取數(shù)據(jù)的性能噪沙,一般來說當然是首推數(shù)組炼彪,但是在數(shù)據(jù)量稍大的容器選擇中,Hashtable將有比數(shù)組性能更高的查詢速度.具體原因看下面的內(nèi)容.
  Hashtable在存儲數(shù)據(jù)時正歼,一般先將該對象的HashCode和0x7FFFFFFF做與操作辐马,因為一個對象的HashCode可以為負數(shù),這樣操作后可以保證它為一個正整數(shù).然后以Hashtable的長度取模局义,得到該對象在Hashtable中的索引.
    index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
  這個對象就會直接放在Hashtable的每index位置喜爷,對于寫入,這和數(shù)組一樣萄唇,把一個對象放在其中的第index位置檩帐,但如果是查詢,經(jīng)過同樣的算法另萤,Hashtable可以直接從第index取得這個對象湃密,而數(shù)組卻要做循環(huán)比較.所以對于數(shù)據(jù)量稍大時,Hashtable的查詢比數(shù)組具有更高的性能.
  既然一個對象可以根據(jù)HashCode直接定位它在Hashtable中的位置四敞,那么為什么Hashtable還要用key來做映射呢?這就是關系Hashtable性能問題的最重要的問題:Hash沖突.
  常見的Hash沖突是不同對象最終產(chǎn)生了相同的索引泛源,而一種非常甚至絕對少見的Hash沖突是,如果一組對象的個數(shù)大過了int范圍忿危,而HashCode的長度只能在int范圍中俩由,所以肯定要有同一組的元素有相同的HashCode,這樣無論如何他們都會有相同的索引.當然這種極端的情況是極少見的癌蚁,可以暫不考慮幻梯,但是對于同的HashCode經(jīng)過取模,則會產(chǎn)中相同的索引努释,或者不同的對象卻具有相同的HashCode碘梢,當然具有相同的索引.
  所以對于索引相同的對象,在該index位置存放了多個值伐蒂,這些值要想能正確區(qū)分煞躬,就要依靠key來識別.
  事實上一個設計各好的HashTable,一般來說會比較平均地分布每個元素,因為Hashtable的長度總是比實際元素的個數(shù)按一定比例進行自增(裝填因子一般為0.75)左右恩沛,這樣大多數(shù)的索引位置只有一個對象在扰,而很少的位置會有幾個元素.所以Hashtable中的每個位置存放的是一個鏈表,對于只有一個對象是位置雷客,鏈表只有一個首節(jié)點(Entry)芒珠,Entry的next為null.然后有hashCode,key搅裙,value屬性保存了該位置的對象的HashCode皱卓,key和value(對象本身),如果有相同索引的對象進來則會進入鏈表的下一個節(jié)點.如果同一個索引中有多個對象部逮,根據(jù)HashCode和key可以在該鏈表中找到一個和查詢的key相匹配的對象.
從上面我看可以看到娜汁,對于HashMap和Hashtable的存取性能有重大影響的首先是應該使該數(shù)據(jù)結構中的元素盡量大可能具有不同的HashCode,雖然這并不能保證不同的HashCode產(chǎn)生不同的index兄朋,但相同的HashCode一定產(chǎn)生相同的index掐禁,從而影響產(chǎn)生Hash沖突.
  對于一個象,如果具有很多屬性颅和,把所有屬性都參與散列傅事,顯然是一種笨拙的設計.因為對象的HashCode()方法幾乎無所不在地被自動調(diào)用,如equals比較融虽,如果太多的對象參與了散列.
那么需要的操作常數(shù)時間將會增加很大.所以享完,挑選哪些屬性參與散列絕對是一個編程水平的問題.
從實現(xiàn)來說灼芭,一般的HashCode方法會這樣:
    return Attribute1.HashCode() Attribute1.HashCode()..[ super.HashCode()]有额,我們知道,每次調(diào)用這個方法彼绷,都要重新對方法內(nèi)的參與散列的對象重新計算一次它們的HashCode的運算巍佑,如果一個對象的屬性沒有改變,仍然要每次都進行計算寄悯,所以如果設置一個標記來緩存當前的散列碼萤衰,只要當參與散列的對象改變時才重新計算,否則調(diào)用緩存的hashCode猜旬,這可以從很大程度上提高性能.
默認的實現(xiàn)是將對象內(nèi)部地址轉化為整數(shù)作為HashCode脆栋,這當然能保證每個對象具有不同的HasCode,因為不同的對象內(nèi)部地址肯定不同(廢話)洒擦,但java語言并不能讓程序員獲取對象內(nèi)部地址椿争,所以,讓每個對象產(chǎn)生不同的HashCode有著很多可研究的技術.
  如果從多個屬性中采樣出能具有平均分布的hashCode的屬性熟嫩,這是一個性能和多樣性相矛盾的地方秦踪,如果所有屬性都參與散列,當然hashCode的多樣性將大大提高,但犧牲了性能椅邓,而如果只能少量的屬性采樣散列柠逞,極端情況會產(chǎn)生大量的散列沖突,如對"人"的屬性中景馁,如果用性別而不是姓名或出生日期板壮,那將只有兩個或幾個可選的hashcode值,將產(chǎn)生一半以上的散列沖突.所以如果可能的條件下裁僧,專門產(chǎn)生一個序列用來生成HashCode將是一個好的選擇(當然產(chǎn)生序列的性能要比所有屬性參與散列的性能高的情況下才行个束,否則還不如直接用所有屬性散列).
如何對HashCode的性能和多樣性求得一個平衡,可以參考相關算法設計的書聊疲,其實并不一定要求非常的優(yōu)秀茬底,只要能盡最大可能減少散列值的聚集.重要的是我們應該記得HashCode對于我們的程序性能有著重要的影響,在程序設計時應該時時加以注意.
  請記谆裰蕖:如果你想有效的使用HashMap阱表,你就必須重寫在其的HashCode()。
還有兩條重寫HashCode()的原則:
不必對每個不同的對象都產(chǎn)生一個唯一的hashcode贡珊,只要你的HashCode方法使get()能夠得到put()放進去的內(nèi)容就可以了最爬。即“不為一原則”。生成hashcode的算法盡量使hashcode的值分散一些门岔, 不要很多hashcode都集中在一個范圍內(nèi)爱致,這樣有利于提高HashMap的性能。即“分散原則”寒随。
  掌握了這兩條原則糠悯,你就能夠用好HashMap編寫自己的程序了。不知道大家注意沒有妻往, java.lang.Object中提供的三個方法:clone()互艾,equals()和hashCode()雖然很典型,但在很多情況下都不能夠適用讯泣,它們只是簡單的由對象的地址得出結果纫普。這就需要我們在自己的程序中重寫它們,其實java類庫中也重寫了千千萬萬個這樣的方法好渠。利用面向?qū)ο蟮亩鄳B(tài)性——覆蓋昨稼,Java的設計者很優(yōu)雅的構建了Java的結構,也更加體現(xiàn)了Java是一門純OOP語言的特性拳锚。
  Java提供的Collection和Map的功能是十分強大的假栓,它們能夠使你的程序?qū)崿F(xiàn)方式更為靈活,執(zhí)行效率更高晌畅。希望本文能夠?qū)Υ蠹腋玫氖褂肏ashMap有所幫助但指。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子棋凳,更是在濱河造成了極大的恐慌拦坠,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剩岳,死亡現(xiàn)場離奇詭異贞滨,居然都是意外死亡,警方通過查閱死者的電腦和手機拍棕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門晓铆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绰播,你說我怎么就攤上這事骄噪。” “怎么了蠢箩?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵链蕊,是天一觀的道長。 經(jīng)常有香客問我谬泌,道長滔韵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任掌实,我火速辦了婚禮陪蜻,結果婚禮上,老公的妹妹穿的比我還像新娘贱鼻。我一直安慰自己宴卖,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布忱嘹。 她就那樣靜靜地躺著嘱腥,像睡著了一般耕渴。 火紅的嫁衣襯著肌膚如雪拘悦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天橱脸,我揣著相機與錄音础米,去河邊找鬼。 笑死添诉,一個胖子當著我的面吹牛屁桑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栏赴,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蘑斧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起竖瘾,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤沟突,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捕传,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惠拭,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年庸论,在試婚紗的時候發(fā)現(xiàn)自己被綠了职辅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡聂示,死狀恐怖域携,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鱼喉,我是刑警寧澤涵亏,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蒲凶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏旋圆。R本人自食惡果不足惜宠默,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灵巧。 院中可真熱鬧搀矫,春花似錦、人聲如沸刻肄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽敏弃。三九已至卦羡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麦到,已是汗流浹背绿饵。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓶颠,地道東北人拟赊。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像粹淋,于是被迫代替她去往敵國和親吸祟。 傳聞我的和親對象是個殘疾皇子瑟慈,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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