equals()和hashCode()區(qū)別据途?
equals():反映的是對(duì)象或變量具體的值,即兩個(gè)對(duì)象里面包含的值--可能是對(duì)象的引用叙甸,也可能是值類型的值颖医。
hashCode():計(jì)算出對(duì)象實(shí)例的哈希碼,并返回哈希碼裆蒸,又稱為散列函數(shù)熔萧。根類Object的hashCode()方法的計(jì)算依賴于對(duì)象實(shí)例的D(內(nèi)存地址),故每個(gè)Object對(duì)象的hashCode都是唯一的僚祷;當(dāng)然佛致,當(dāng)對(duì)象所對(duì)應(yīng)的類重寫了hashCode()方法時(shí),結(jié)果就截然不同了辙谜。
之所以有hashCode方法俺榆,是因?yàn)樵谂康膶?duì)象比較中,hashCode要比equals來(lái)得快装哆,很多集合都用到了hashCode罐脊,比如HashTable。
兩個(gè)obj蜕琴,如果equals()相等萍桌,hashCode()一定相等。
兩個(gè)obj奸绷,如果hashCode()相等梗夸,equals()不一定相等(Hash散列值有沖突的情況,雖然概率很低)号醉。
所以:
可以考慮在集合中反症,判斷兩個(gè)對(duì)象是否相等的規(guī)則是:
第一步,如果hashCode()相等畔派,則查看第二步铅碍,否則不相等;
第二步,查看equals()是否相等线椰,如果相等胞谈,則兩obj相等,否則還是不相等憨愉。
1烦绳、首先equals()和hashcode()這兩個(gè)方法都是從object類中繼承過(guò)來(lái)的。
equals()是對(duì)兩個(gè)對(duì)象的地址值進(jìn)行的比較(即比較引用是否相同)配紫。
hashCode()是一個(gè)本地方法径密,它的實(shí)現(xiàn)是根據(jù)本地機(jī)器相關(guān)的。
2躺孝、Java語(yǔ)言對(duì)equals()的要求如下享扔,這些要求是必須遵循的:
A對(duì)稱性:如果x.equals(y)返回是“true”底桂,那么y.equals(x)也應(yīng)該返回是“true”。
B反射性:x.equals(x)必須返回是“true”惧眠。
C類推性:如果x.equals(y)返回是“true”籽懦,而且y.equals(z)返回是“true”,那么z.equals(x)也應(yīng)該返回是“true”氛魁。
D一致性:如果x.equals(y)返回是“true”暮顺,只要x和y內(nèi)容一直不變,不管你重復(fù)x.equals(y)多少次呆盖,返回都是“true”拖云。
任何情況下,x.equals(null)应又,永遠(yuǎn)返回是“false”;x.equals(和x不同類型的對(duì)象)永遠(yuǎn)返回是“false”乏苦。
3株扛、equals()相等的兩個(gè)對(duì)象,hashcode()一定相等汇荐;
反過(guò)來(lái):hashcode()不等洞就,一定能推出equals()也不等;
hashcode()相等掀淘,equals()可能相等旬蟋,也可能不等。
為什么選擇hashcode方法革娄?
以java.lang.Object來(lái)理解,JVM每new一個(gè)Object,它都會(huì)將這個(gè)Object丟到一個(gè)Hash哈希表中去,這樣的話,下次做Object的比較或者取這個(gè)對(duì)象的時(shí)候,它會(huì)根據(jù)對(duì)象的hashcode再?gòu)腍ash表中取這個(gè)對(duì)象倾贰。這樣做的目的是提高取對(duì)象的效率。具體過(guò)程是這樣:
1.newObject(),JVM根據(jù)這個(gè)對(duì)象的Hashcode值,放入到對(duì)應(yīng)的Hash表對(duì)應(yīng)的Key上,如果不同的對(duì)象確產(chǎn)生了相同的hash值,也就是發(fā)生了Hashkey相同導(dǎo)致沖突的情況,那么就在這個(gè)Hashkey的地方產(chǎn)生一個(gè)鏈表,將所有產(chǎn)生相同hashcode的對(duì)象放到這個(gè)單鏈表上去,串在一起拦惋。
2.比較兩個(gè)對(duì)象的時(shí)候,首先根據(jù)他們的hashcode去hash表中找他的對(duì)象,當(dāng)兩個(gè)對(duì)象的hashcode相同,那么就是說(shuō)他們這兩個(gè)對(duì)象放在Hash表中的同一個(gè)key上,那么他們一定在這個(gè)key上的鏈表上匆浙。那么此時(shí)就只能根據(jù)Object的equal方法來(lái)比較這個(gè)對(duì)象是否equal。當(dāng)兩個(gè)對(duì)象的hashcode不同的話厕妖,肯定他們不能equal.
下面我舉個(gè)例子詳細(xì)說(shuō)明下首尼。
list是可以重復(fù)的,set是不可以重復(fù)的言秸。那么set存儲(chǔ)數(shù)據(jù)的時(shí)候是怎樣判斷存進(jìn)的數(shù)據(jù)是否已經(jīng)存在软能。使用equals()方法呢,還是hashcode()方法举畸。
假如用equals()查排,那么存儲(chǔ)一個(gè)元素就要跟已存在的所有元素比較一遍,比如已存入100個(gè)元素俱恶,那么存101個(gè)元素的時(shí)候雹嗦,就要調(diào)用equals方法100次范舀。
但如果用hashcode()方法的話,他就利用了hash算法來(lái)存儲(chǔ)數(shù)據(jù)的了罪。這樣的話每存一個(gè)數(shù)據(jù)就調(diào)用一次hashcode()方法锭环,得到一個(gè)hashcode值及存入的位置。如果該位置不存在數(shù)據(jù)那么就直接存入泊藕,否則調(diào)用一次equals()方法辅辩,不相同則存,相同不存娃圆。這樣下來(lái)整個(gè)存儲(chǔ)下來(lái)不需要調(diào)用幾次equals方法玫锋,雖然多了幾次hashcode方法,但相對(duì)于前面來(lái)講效率高了不少讼呢。
為什么要重寫equals方法撩鹿?
因?yàn)镺bject的equal方法默認(rèn)是兩個(gè)對(duì)象的引用的比較,意思就是指向同一內(nèi)存,地址則相等悦屏,否則不相等节沦;如果你現(xiàn)在需要利用對(duì)象里面的值來(lái)判斷是否相等,則重載equal方法础爬。
說(shuō)道這個(gè)地方我相信很多人會(huì)有疑問甫贯,相信大家都被String對(duì)象的equals()方法和"=="糾結(jié)過(guò)一段時(shí)間,當(dāng)時(shí)我們知道String對(duì)象中equals方法是判斷值的看蚜,而==是地址判斷叫搁。
那照這么說(shuō)equals怎么會(huì)是地址的比較呢?
那是因?yàn)閷?shí)際上JDK中供炎,String渴逻、Math等封裝類都對(duì)Object中的equals()方法進(jìn)行了重寫。
我們先看看Object中equals方法的源碼:
1.publicbooleanequals(Objectobj){
2.return(this==obj);
3.}
我們都知道所有的對(duì)象都擁有標(biāo)識(shí)(內(nèi)存地址)和狀態(tài)(數(shù)據(jù))碱茁,同時(shí)“==”比較兩個(gè)對(duì)象的的內(nèi)存地址裸卫,所以說(shuō)使用Object的equals()方法是比較兩個(gè)對(duì)象的內(nèi)存地址是否相等,即若object1.equals(object2)為true纽竣,則表示equals1和equals2實(shí)際上是引用同一個(gè)對(duì)象墓贿。雖然有時(shí)候Object的equals()方法可以滿足我們一些基本的要求,但是我們必須要清楚我們很大部分時(shí)間都是進(jìn)行兩個(gè)對(duì)象的比較蜓氨,這個(gè)時(shí)候Object的equals()方法就不可以了聋袋,所以才會(huì)有String這些類對(duì)equals方法的改寫,依次類推Double穴吹、Integer幽勒、Math。港令。啥容。锈颗。等等這些類都是重寫了equals()方法的,從而進(jìn)行的是內(nèi)容的比較咪惠。希望大家不要搞混了击吱。
改寫equals時(shí)總是要改寫hashcode
這一點(diǎn)上《Effectivejava》中點(diǎn)7條和第8條講的很詳細(xì),如果看完該博客后你還不懂得話遥昧,可以看看覆醇。里面也涉及到hashcode的設(shè)計(jì),由于內(nèi)容較深炭臭,我覺得我還接受不了永脓,所以就不討論這個(gè)了。
java.lnag.Object中對(duì)hashCode的約定:
1.在一個(gè)應(yīng)用程序執(zhí)行期間鞋仍,如果一個(gè)對(duì)象的equals方法做比較所用到的信息沒有被修改的話常摧,則對(duì)該對(duì)象調(diào)用hashCode方法多次,它必須始終如一地返回同一個(gè)整數(shù)凿试。
2.如果兩個(gè)對(duì)象根據(jù)equals(Objecto)方法是相等的排宰,則調(diào)用這兩個(gè)對(duì)象中任一對(duì)象的hashCode方法必須產(chǎn)生相同的整數(shù)結(jié)果。
3.如果兩個(gè)對(duì)象根據(jù)equals(Objecto)方法是不相等的那婉,則調(diào)用這兩個(gè)對(duì)象中任一個(gè)對(duì)象的hashCode方法,不要求產(chǎn)生不同的整數(shù)結(jié)果党瓮。但如果能不同详炬,則可能提高散列表的性能。
根據(jù)上一個(gè)問題寞奸,實(shí)際上我們已經(jīng)能很簡(jiǎn)單的解釋這一點(diǎn)了呛谜,比如改寫String中的equals為基于內(nèi)容上的比較而不是內(nèi)存地址的話,那么雖然equals相等枪萄,但并不代表內(nèi)存地址相等隐岛,由hashcode方法的定義可知內(nèi)存地址不同,沒改寫的hashcode值也可能不同瓷翻。所以違背了第二條約定聚凹。
又如new一個(gè)對(duì)象,再new一個(gè)內(nèi)容相等的對(duì)象齐帚,調(diào)用equals方法返回的true妒牙,但他們的hashcode值不同,將兩個(gè)對(duì)象存入HashSet中对妄,會(huì)使得其中包含兩個(gè)相等的對(duì)象湘今,因?yàn)槭窍葯z索hashcode值,不等的情況下才會(huì)去比較equals方法的剪菱。
hashCode方法使用介紹
Hash表數(shù)據(jù)結(jié)構(gòu)常識(shí):
哈希表基于數(shù)組摩瞎。
缺點(diǎn):基于數(shù)組的拴签,數(shù)組創(chuàng)建后難以擴(kuò)展。某些哈希表被基本填滿時(shí)旗们,性能下降得非常嚴(yán)重蚓哩。
沒有一種簡(jiǎn)便得方法可以以任何一種順序遍歷表中數(shù)據(jù)項(xiàng)。
如果不需要有序遍歷數(shù)據(jù)蚪拦,并且可以提前預(yù)測(cè)數(shù)據(jù)量的大小杖剪,那么哈希表在速度和易用性方面是無(wú)與倫比的。
為什么HashCode對(duì)于對(duì)象是如此的重要:
一個(gè)對(duì)象的HashCode就是一個(gè)簡(jiǎn)單的Hash算法的實(shí)現(xiàn)驰贷,雖然它和那些真正的復(fù)雜的Hash算法相比還不能叫真正的算法盛嘿,它如何實(shí)現(xiàn)它,不僅僅是程序員的編程水平問題括袒,而是關(guān)系到你的對(duì)象在存取是性能的非常重要的關(guān)系.有可能次兆,不同的HashCode可能會(huì)使你的對(duì)象存取產(chǎn)生,成百上千倍的性能差別.
先來(lái)看一下锹锰,在Java中兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu):HashMap和Hashtable芥炭,雖然它們有很大的區(qū)別,如繼承關(guān)系不同恃慧,對(duì)value的約束條件(是否允許null)不同园蝠,以及線程安全性等有著特定的區(qū)別,但從實(shí)現(xiàn)原理上來(lái)說(shuō)痢士,它們是一致的.所以彪薛,我們只以Hashtable來(lái)說(shuō)明:
在java中,存取數(shù)據(jù)的性能怠蹂,一般來(lái)說(shuō)當(dāng)然是首推數(shù)組善延,但是在數(shù)據(jù)量稍大的容器選擇中,Hashtable將有比數(shù)組性能更高的查詢速度.具體原因看下面的內(nèi)容.
Hashtable在存儲(chǔ)數(shù)據(jù)時(shí)城侧,一般先將該對(duì)象的HashCode和0x7FFFFFFF做與操作易遣,因?yàn)橐粋€(gè)對(duì)象的HashCode可以為負(fù)數(shù),這樣操作后可以保證它為一個(gè)正整數(shù).然后以Hashtable的長(zhǎng)度取模嫌佑,得到該對(duì)象在Hashtable中的索引.
index=(o.hashCode()&0x7FFFFFFF)%hs.length;
這個(gè)對(duì)象就會(huì)直接放在Hashtable的每index位置豆茫,對(duì)于寫入,這和數(shù)組一樣歧强,把一個(gè)對(duì)象放在其中的第index位置澜薄,但如果是查詢,經(jīng)過(guò)同樣的算法摊册,Hashtable可以直接從第index取得這個(gè)對(duì)象肤京,而數(shù)組卻要做循環(huán)比較.所以對(duì)于數(shù)據(jù)量稍大時(shí),Hashtable的查詢比數(shù)組具有更高的性能.既然一個(gè)對(duì)象可以根據(jù)HashCode直接定位它在Hashtable中的位置,那么為什么Hashtable還要用key來(lái)做映射呢?這就是關(guān)系Hashtable性能問題的最重要的問題:Hash沖突.
常見的Hash沖突是不同對(duì)象最終產(chǎn)生了相同的索引忘分,而一種非常甚至絕對(duì)少見的Hash沖突是棋枕,如果一組對(duì)象的個(gè)數(shù)大過(guò)了int范圍,而HashCode的長(zhǎng)度只能在int范圍中妒峦,所以肯定要有同一組的元素有相同的HashCode重斑,這樣無(wú)論如何他們都會(huì)有相同的索引.當(dāng)然這種極端的情況是極少見的,可以暫不考慮肯骇,但是對(duì)于同的HashCode經(jīng)過(guò)取模窥浪,則會(huì)產(chǎn)中相同的索引,或者不同的對(duì)象卻具有相同的HashCode笛丙,當(dāng)然具有相同的索引.所以對(duì)于索引相同的對(duì)象漾脂,在該index位置存放了多個(gè)值,這些值要想能正確區(qū)分胚鸯,就要依靠key來(lái)識(shí)別.
事實(shí)上一個(gè)設(shè)計(jì)各好的HashTable骨稿,一般來(lái)說(shuō)會(huì)比較平均地分布每個(gè)元素,因?yàn)镠ashtable的長(zhǎng)度總是比實(shí)際元素的個(gè)數(shù)按一定比例進(jìn)行自增(裝填因子一般為0.75)左右姜钳,這樣大多數(shù)的索引位置只有一個(gè)對(duì)象坦冠,而很少的位置會(huì)有幾個(gè)元素.所以Hashtable中的每個(gè)位置存放的是一個(gè)鏈表,對(duì)于只有一個(gè)對(duì)象是位置哥桥,鏈表只有一個(gè)首節(jié)點(diǎn)(Entry)辙浑,Entry的next為null.然后有hashCode,key拟糕,value屬性保存了該位置的對(duì)象的HashCode例衍,key和value(對(duì)象本身),如果有相同索引的對(duì)象進(jìn)來(lái)則會(huì)進(jìn)入鏈表的下一個(gè)節(jié)點(diǎn).如果同一個(gè)索引中有多個(gè)對(duì)象已卸,根據(jù)HashCode和key可以在該鏈表中找到一個(gè)和查詢的key相匹配的對(duì)象.
從上面我看可以看到,對(duì)于HashMap和Hashtable的存取性能有重大影響的首先是應(yīng)該使該數(shù)據(jù)結(jié)構(gòu)中的元素盡量大可能具有不同的HashCode硼一,雖然這并不能保證不同的HashCode產(chǎn)生不同的index累澡,但相同的HashCode一定產(chǎn)生相同的index,從而影響產(chǎn)生Hash沖突.對(duì)于一個(gè)象般贼,如果具有很多屬性愧哟,把所有屬性都參與散列,顯然是一種笨拙的設(shè)計(jì).因?yàn)閷?duì)象的HashCode()方法幾乎無(wú)所不在地被自動(dòng)調(diào)用哼蛆,如equals比較蕊梧,如果太多的對(duì)象參與了散列.那么需要的操作常數(shù)時(shí)間將會(huì)增加很大.所以,挑選哪些屬性參與散列絕對(duì)是一個(gè)編程水平的問題.從實(shí)現(xiàn)來(lái)說(shuō)腮介,一般的HashCode方法會(huì)這樣:
returnAttribute1.HashCode()Attribute1.HashCode()..[super.HashCode()]肥矢,
我們知道,每次調(diào)用這個(gè)方法叠洗,都要重新對(duì)方法內(nèi)的參與散列的對(duì)象重新計(jì)算一次它們的HashCode的運(yùn)算甘改,如果一個(gè)對(duì)象的屬性沒有改變旅东,仍然要每次都進(jìn)行計(jì)算,所以如果設(shè)置一個(gè)標(biāo)記來(lái)緩存當(dāng)前的散列碼十艾,只要當(dāng)參與散列的對(duì)象改變時(shí)才重新計(jì)算抵代,否則調(diào)用緩存的hashCode,這可以從很大程度上提高性能.
默認(rèn)的實(shí)現(xiàn)是將對(duì)象內(nèi)部地址轉(zhuǎn)化為整數(shù)作為HashCode忘嫉,這當(dāng)然能保證每個(gè)對(duì)象具有不同的HasCode荤牍,因?yàn)椴煌膶?duì)象內(nèi)部地址肯定不同(廢話),但java語(yǔ)言并不能讓程序員獲取對(duì)象內(nèi)部地址庆冕,所以康吵,讓每個(gè)對(duì)象產(chǎn)生不同的HashCode有著很多可研究的技術(shù).
如果從多個(gè)屬性中采樣出能具有平均分布的hashCode的屬性,這是一個(gè)性能和多樣性相矛盾的地方愧杯,如果所有屬性都參與散列涎才,當(dāng)然hashCode的多樣性將大大提高,但犧牲了性能力九,而如果只能少量的屬性采樣散列耍铜,極端情況會(huì)產(chǎn)生大量的散列沖突,如對(duì)"人"的屬性中跌前,如果用性別而不是姓名或出生日期棕兼,那將只有兩個(gè)或幾個(gè)可選的hashcode值,將產(chǎn)生一半以上的散列沖突.所以如果可能的條件下抵乓,專門產(chǎn)生一個(gè)序列用來(lái)生成HashCode將是一個(gè)好的選擇(當(dāng)然產(chǎn)生序列的性能要比所有屬性參與散列的性能高的情況下才行伴挚,否則還不如直接用所有屬性散列).
如何對(duì)HashCode的性能和多樣性求得一個(gè)平衡,可以參考相關(guān)算法設(shè)計(jì)的書灾炭,其實(shí)并不一定要求非常的優(yōu)秀茎芋,只要能盡最大可能減少散列值的聚集.重要的是我們應(yīng)該記得HashCode對(duì)于我們的程序性能有著重要的影響,在程序設(shè)計(jì)時(shí)應(yīng)該時(shí)時(shí)加以注意.
請(qǐng)記昨诔觥:如果你想有效的使用HashMap田弥,你就必須重寫在其的HashCode()。
還有兩條重寫HashCode()的原則:
不必對(duì)每個(gè)不同的對(duì)象都產(chǎn)生一個(gè)唯一的hashcode铡原,只要你的HashCode方法使get()能夠得到put()放進(jìn)去的內(nèi)容就可以了偷厦。即“不為一原則”。生成hashcode的算法盡量使hashcode的值分散一些燕刻,不要很多hashcode都集中在一個(gè)范圍內(nèi)只泼,這樣有利于提高HashMap的性能。即“分散原則”卵洗。至于第二條原則的具體原因请唱,有興趣者可以參考BruceEckel的《ThinkinginJava》,在那里有對(duì)HashMap內(nèi)部實(shí)現(xiàn)原理的介紹,這里就不贅述了籍滴。掌握了這兩條原則酪夷,你就能夠用好HashMap編寫自己的程序了。不知道大家注意沒有孽惰,java.lang.Object中提供的三個(gè)方法:clone()晚岭,equals()和hashCode()雖然很典型,但在很多情況下都不能夠適用勋功,它們只是簡(jiǎn)單的由對(duì)象的地址得出結(jié)果坦报。這就需要我們?cè)谧约旱某绦蛑兄貙懰鼈儯鋵?shí)java類庫(kù)中也重寫了千千萬(wàn)萬(wàn)個(gè)這樣的方法狂鞋。利用面向?qū)ο蟮亩鄳B(tài)性——覆蓋片择,Java的設(shè)計(jì)者很優(yōu)雅的構(gòu)建了Java的結(jié)構(gòu),也更加體現(xiàn)了Java是一門純OOP語(yǔ)言的特性骚揍。
Java提供的Collection和Map的功能是十分強(qiáng)大的字管,它們能夠使你的程序?qū)崿F(xiàn)方式更為靈活,執(zhí)行效率更高信不。希望本文能夠?qū)Υ蠹腋玫氖褂肏ashMap有所幫助嘲叔。
為了讓學(xué)習(xí)變得輕松、高效抽活,今天給大家免費(fèi)分享一套Java入門教學(xué)資源硫戈。幫助大家在成為Java架構(gòu)師的道路上披荊斬棘。需要資料的歡迎加入學(xué)習(xí)交流群:9285下硕,05736