Java中的hashCode() 和 equals()的若干問(wèn)題解答

一、hashCode()的作用
哈希表這個(gè)數(shù)據(jù)結(jié)構(gòu)想必大多數(shù)人都不陌生,而且在很多地方都會(huì)利用到hash表來(lái)提高查找效率。在Java的Object類中有一個(gè)方法:
public?native?int?hashCode();根據(jù)這個(gè)方法的聲明可知,該方法返回一個(gè)int類型的數(shù)值,并且是本地方法,因此在Object類中并沒(méi)有給出具體的實(shí)現(xiàn)。為何Object類需要這樣一個(gè)方法?它有什么作用呢?
不妨舉個(gè)例子:
假設(shè)內(nèi)存中有0 1 2 3 4 5 6 7 8這8個(gè)位置,如果我有個(gè)字段叫做ID,那么我要把這個(gè)字段存放在以上8個(gè)位置之一,如果不用HashCode而任意存放,那么當(dāng)查找時(shí)就需要到8個(gè)位置中去挨個(gè)查找。使用HashCode則效率會(huì)快很多,把ID的HashCode%8,然后把ID存放在取得余數(shù)的那個(gè)位置,然后每次查找該類的時(shí)候都可以通過(guò)ID的HashCode%8求余數(shù)直接找到存放的位置了。如果ID的HashCode%8算出來(lái)的位置上本身已經(jīng)有數(shù)據(jù)了怎么辦?這就取決于算法的實(shí)現(xiàn)了,比如ThreadLocal中的做法就是從算出來(lái)的位置向后查找第一個(gè)為空的位置,放置數(shù)據(jù);HashMap的做法就是通過(guò)鏈?zhǔn)浇Y(jié)構(gòu)連起來(lái)。反正,只要保證放的時(shí)候和取的時(shí)候的算法一致就行了。如果ID的HashCode%8相等怎么辦(這種對(duì)應(yīng)的是上句說(shuō)的鏈?zhǔn)浇Y(jié)構(gòu)的場(chǎng)景)?這時(shí)候就需要定義equals了。先通過(guò)HashCode%8來(lái)判斷類在哪一個(gè)位置,再通過(guò)equals來(lái)在這個(gè)位置上尋找需要的類。對(duì)比兩個(gè)類的時(shí)候也差不多,先通過(guò)HashCode比較,假如HashCode相等再判斷equals。如果兩個(gè)類的HashCode都不相同,那么這兩個(gè)類必定是不同的。
?????
再舉個(gè)實(shí)際的例子Set。我們知道Set里面的元素是不可以重復(fù)的,那么如何做到?Set是根據(jù)equals()方法來(lái)判斷兩個(gè)元素是否相等的。比方說(shuō)Set里面已經(jīng)有1000個(gè)元素了,那么第1001個(gè)元素進(jìn)來(lái)的時(shí)候,最多可能調(diào)用1000次equals方法,如果equals方法寫得復(fù)雜,對(duì)比的東西特別多,那么效率會(huì)大大降低。使用HashCode就不一樣了,比方說(shuō)HashSet,底層是基于HashMap實(shí)現(xiàn)的,先通過(guò)HashCode取一個(gè)模,這樣一下子就固定到某個(gè)位置了,如果這個(gè)位置上沒(méi)有元素,那么就可以肯定HashSet中必定沒(méi)有和新添加的元素equals的元素,就可以直接存放了,都不需要比較;如果這個(gè)位置上有元素了,逐一比較,比較的時(shí)候先比較HashCode,HashCode都不同接下去都不用比了,肯定不一樣,HashCode相等,再equals比較,沒(méi)有相同的元素就存,有相同的元素就不存。如果原來(lái)的Set里面有相同的元素,只要HashCode的生成方式定義得好(不重復(fù)),不管Set里面原來(lái)有多少元素,只需要執(zhí)行一次的equals就可以了。這樣一來(lái),實(shí)際調(diào)用equals方法的次數(shù)大大降低,提高了效率。
?????
所以hashCode在上面扮演的角色為尋域(尋找某個(gè)對(duì)象在集合中區(qū)域位置)。hashCode可以將集合分成若干個(gè)區(qū)域,每個(gè)對(duì)象都可以計(jì)算出他們的hash碼,可以將hash碼分組,每個(gè)分組對(duì)應(yīng)著某個(gè)存儲(chǔ)區(qū)域,根據(jù)一個(gè)對(duì)象的hash碼就可以確定該對(duì)象所存儲(chǔ)區(qū)域,這樣就大大減少查詢匹配元素的數(shù)量,提高了查詢效率。
?????
即hashCode() 的作用是獲取哈希碼,也稱為散列碼;它實(shí)際上是返回一個(gè)int整數(shù)。這個(gè)哈希碼的作用是確定該對(duì)象在哈希表中的索引位置。
二、hashCode對(duì)于一個(gè)對(duì)象的重要性
?????
hashCode() 定義在JDK的Object.java中,這就意味著Java中的任何類都包含有hashCode() 函數(shù)。
??????
雖然,每個(gè)Java類都包含hashCode() 函數(shù)。但是,僅僅當(dāng)創(chuàng)建某個(gè)“類的散列表”(關(guān)于“散列表”見下面說(shuō)明)時(shí),該類的hashCode() 才有用(作用是:確定該類的每一個(gè)對(duì)象在散列表中的位置;其它情況下(例如,創(chuàng)建類的單個(gè)對(duì)象,或者創(chuàng)建類的對(duì)象數(shù)組等等),類的hashCode() 沒(méi)有作用。
??????
上面的散列表,指的是:Java集合中本質(zhì)是散列表的類,如HashMap,Hashtable,HashSet。
??????
也就是說(shuō):hashCode() 在散列表中才有用,在其它情況下沒(méi)用。在散列表中hashCode() 的作用是獲取對(duì)象的散列碼,進(jìn)而確定該對(duì)象在散列表中的位置。
??????
OK!至此,我們搞清楚了:hashCode()的作用是獲取散列碼。但是,散列碼是用來(lái)干什么的呢?這里簡(jiǎn)單的介紹一下散列碼的作用。
?????
我們都知道,散列表存儲(chǔ)的是鍵值對(duì)(key-value),它的特點(diǎn)是:能根據(jù)“鍵”快速的檢索出對(duì)應(yīng)的“值”。這其中就利用到了散列碼!散列表的本質(zhì)是通過(guò)數(shù)組實(shí)現(xiàn)的。當(dāng)我們要獲取散列表中的某個(gè)“值”時(shí),實(shí)際上是要獲取數(shù)組中的某個(gè)位置的元素。而數(shù)組的位置,就是通過(guò)“鍵”來(lái)獲取的;更進(jìn)一步說(shuō),數(shù)組的位置,是通過(guò)“鍵”對(duì)應(yīng)的散列碼計(jì)算得到的。
?????
下面我以HashTable為例闡述hashCode對(duì)于一個(gè)對(duì)象的重要性。
??????
一個(gè)對(duì)象勢(shì)必會(huì)存在若干個(gè)屬性,如何選擇屬性來(lái)進(jìn)行散列考驗(yàn)著一個(gè)人的設(shè)計(jì)能力。如果我們將所有屬性進(jìn)行散列,這必定會(huì)是一個(gè)糟糕的設(shè)計(jì),因?yàn)閷?duì)象的hashCode方法無(wú)時(shí)無(wú)刻不是在被調(diào)用,如果太多的屬性參與散列,那么需要的操作數(shù)時(shí)間將會(huì)大大增加,這將嚴(yán)重影響程序的性能。但是如果較少屬相參與散列,散列的多樣性會(huì)削弱,會(huì)產(chǎn)生大量的散列“沖突”,除了不能夠很好的利用空間外,在某種程度也會(huì)影響對(duì)象的查詢效率。其實(shí)這兩者是一個(gè)矛盾體,散列的多樣性會(huì)帶來(lái)性能的降低。
??????
那么如何對(duì)對(duì)象的hashCode進(jìn)行設(shè)計(jì),LZ也沒(méi)有經(jīng)驗(yàn)。從網(wǎng)上查到了這樣一種解決方案:設(shè)置一個(gè)緩存標(biāo)識(shí)來(lái)緩存當(dāng)前的散列碼,只有當(dāng)參與散列的對(duì)象改變時(shí)才會(huì)重新計(jì)算,否則調(diào)用緩存的hashCode,這樣就可以從很大程度上提高性能。
??????
在HashTable計(jì)算某個(gè)對(duì)象在table[]數(shù)組中的索引位置,其代碼如下:
int?index = (hash & 0x7FFFFFFF) % tab.length;為什么要&0x7FFFFFFF?因?yàn)槟承?duì)象的hashCode可能會(huì)為負(fù)值,與0x7FFFFFFF進(jìn)行與運(yùn)算可以確保index為一個(gè)正數(shù)。通過(guò)這步我可以直接定位某個(gè)對(duì)象的位置,所以從理論上來(lái)說(shuō)我們是完全可以利用hashCode直接定位對(duì)象的散列表中的位置,但是為什么會(huì)存在一個(gè)key-value的鍵值對(duì),利用key的hashCode來(lái)存入數(shù)據(jù)而不是直接存放value呢?這就關(guān)系HashTable性能問(wèn)題的最重要的問(wèn)題:Hash沖突!
??????
我們知道沖突的產(chǎn)生是由于不同的對(duì)象產(chǎn)生了相同的散列碼,假如我們?cè)O(shè)計(jì)對(duì)象的散列碼可以確保99.999999999%的不重復(fù),但是有一種絕對(duì)且?guī)缀醪豢赡苡龅降臎_突你是絕對(duì)避免不了的。我們知道hashcode返回的是int,它的值只可能在int范圍內(nèi)。如果我們存放的數(shù)據(jù)超過(guò)了int的范圍呢?這樣就必定會(huì)產(chǎn)生兩個(gè)相同的index,這時(shí)在index位置處會(huì)存儲(chǔ)兩個(gè)對(duì)象,我們就可以利用key本身來(lái)進(jìn)行判斷。所以具有相索引的對(duì)象,在該index位置處存在多個(gè)對(duì)象,我們必須依靠key的hashCode和key本身來(lái)進(jìn)行區(qū)分。
三、equals()的作用
equals() 的作用是 用來(lái)判斷兩個(gè)對(duì)象是否相等。
equals() 定義在JDK的Object.java中。通過(guò)判斷兩個(gè)對(duì)象的地址是否相等(即,是否是同一個(gè)對(duì)象)來(lái)區(qū)分它們是否相等。源碼如下:
public?boolean?equals(Object?obj) {
????return?(this?== obj);
}既然Object.java中定義了equals()方法,這就意味著所有的Java類都實(shí)現(xiàn)了equals()方法,所有的類都可以通過(guò)equals()去比較兩個(gè)對(duì)象是否相等。但是,我們已經(jīng)說(shuō)過(guò),使用默認(rèn)的“equals()”方法,等價(jià)于“==”方法。因此,我們通常會(huì)重寫equals()方法:若兩個(gè)對(duì)象的內(nèi)容相等,則equals()方法返回true;否則,返回fasle。
下面根據(jù)“類是否覆蓋equals()方法”,將它分為2類。
(01) 若某個(gè)類沒(méi)有覆蓋equals()方法,當(dāng)它的通過(guò)equals()比較兩個(gè)對(duì)象時(shí),實(shí)際上是比較兩個(gè)對(duì)象是不是同一個(gè)對(duì)象。這時(shí),等價(jià)于通過(guò)“==”去比較這兩個(gè)對(duì)象。
(02) 我們可以覆蓋類的equals()方法,來(lái)讓equals()通過(guò)其它方式比較兩個(gè)對(duì)象是否相等。通常的做法是:若兩個(gè)對(duì)象的內(nèi)容相等,則equals()方法返回true;否則,返回fasle。
下面,舉例對(duì)上面的2種情況進(jìn)行說(shuō)明。
1、"沒(méi)有覆蓋equals()方法"的情況
代碼如下:
package com.demo;
public?class?EqualsTest1?{
????
????/**
?????* Person類
?????* @author lixiaoxi
?????*
?????*/
????private?static?class?Person{
????????int?age;
????????String name;
????????
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?name + " - "?+age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
????????// 再用equals比較它們是否相等
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????System.out.printf("%s\n", p1.equals(p2));
????}
????
}運(yùn)行結(jié)果:false
結(jié)果分析:
?????
我們通過(guò) p1.equals(p2) 來(lái)“比較p1和p2是否相等時(shí)”。實(shí)際上,調(diào)用的Object.java的equals()方法,即調(diào)用的 (p1==p2) 。它是比較“p1和p2是否是同一個(gè)對(duì)象”。而由 p1 和 p2 的定義可知,它們雖然內(nèi)容相同;但它們是兩個(gè)不同的對(duì)象!因此,返回結(jié)果是false。
2、"覆蓋equals()方法"的情況
我們修改上面的EqualsTest1.java:覆蓋equals()方法。
代碼如下:
package com.demo;
public?class?EqualsTest2?{
????
????/**
?????* Person類
?????* @author lixiaoxi
?????*
?????*/
????private?static?class?Person{
????????int?age;
????????String name;
????????
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?name + " - "?+age;
????????}
????????
????????/**
?????????* 覆蓋equals方法
?????????*/
????????@Override
????????public?boolean equals(Object obj){
????????????if(obj == null){
????????????????return?false;
????????????}
????????????
????????????//如果是同一個(gè)對(duì)象返回true,反之返回false
????????????if(this?== obj){
????????????????return?true;
????????????}
????????????
????????????//判斷是否類型相同
????????????if(this.getClass() != obj.getClass()){
????????????????return?false;
????????????}
????????????
????????????Person person = (Person)obj;
????????????return?name.equals(person.name) && age==person.age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
????????// 再用equals比較它們是否相等
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????System.out.printf("%s\n", p1.equals(p2));
????}
}運(yùn)行結(jié)果:true
結(jié)果分析:
我們?cè)贓qualsTest2.java 中重寫了Person的equals()函數(shù):當(dāng)兩個(gè)Person對(duì)象的 name 和 age 都相等,則返回true。因此,運(yùn)行結(jié)果返回true。
講到這里,順便說(shuō)一下java對(duì)equals()的要求。有以下幾點(diǎn):
1. 對(duì)稱性:如果x.equals(y)返回是"true",那么y.equals(x)也應(yīng)該返回是"true"。
2. 反射性:x.equals(x)必須返回是"true"。
3. 類推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也應(yīng)該返回是"true"。
4. 一致性:如果x.equals(y)返回是"true",只要x和y內(nèi)容一直不變,不管你重復(fù)x.equals(y)多少次,返回都是"true"。
5. 非空性,x.equals(null),永遠(yuǎn)返回是"false";x.equals(和x不同類型的對(duì)象)永遠(yuǎn)返回是"false"。
四、equals()與==的區(qū)別
== : 它的作用是判斷兩個(gè)對(duì)象的地址是不是相等。即,判斷兩個(gè)對(duì)象是不是同一個(gè)對(duì)象。
equals() : 它的作用也是判斷兩個(gè)對(duì)象是否相等。但它一般有兩種使用情況(上面已詳細(xì)介紹過(guò)):
情況1,類沒(méi)有覆蓋equals()方法。則通過(guò)equals()比較該類的兩個(gè)對(duì)象時(shí),等價(jià)于通過(guò)“==”比較這兩個(gè)對(duì)象。
情況2,類覆蓋了equals()方法。一般,我們都覆蓋equals()方法來(lái)比較兩個(gè)對(duì)象的內(nèi)容相等;若它們的內(nèi)容相等,則返回true(即,認(rèn)為這兩個(gè)對(duì)象相等)。
下面,通過(guò)示例比較它們的區(qū)別。
代碼如下:
package com.demo;
public?class?EqualsTest3?{
????
????/**
?????* Person類
?????* @author lixiaoxi
?????*
?????*/
????private?static?class?Person{
????????int?age;
????????String name;
????????
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?name + " - "?+age;
????????}
????????
????????/**
?????????* 覆蓋equals方法
?????????*/
????????@Override
????????public?boolean equals(Object obj){
????????????if(obj == null){
????????????????return?false;
????????????}
????????????
????????????//如果是同一個(gè)對(duì)象返回true,反之返回false
????????????if(this?== obj){
????????????????return?true;
????????????}
????????????
????????????//判斷是否類型相同
????????????if(this.getClass() != obj.getClass()){
????????????????return?false;
????????????}
????????????
????????????Person person = (Person)obj;
????????????return?name.equals(person.name) && age==person.age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
????????// 再用equals比較它們是否相等
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????System.out.printf("p1.equals(p2) : %s\n", p1.equals(p2));
????????System.out.printf("p1==p2 : %s\n", p1==p2);
????}
}運(yùn)行結(jié)果:
p1.equals(p2) : true
p1==p2 : false結(jié)果分析:
在EqualsTest3.java 中:
(01) p1.equals(p2) 這是判斷p1和p2的內(nèi)容是否相等。因?yàn)镻erson覆蓋equals()方法,而這個(gè)equals()是用來(lái)判斷p1和p2的內(nèi)容是否相等,恰恰p1和p2的內(nèi)容又相等;因此,返回true。
(02) p1==p2 這是判斷p1和p2是否是同一個(gè)對(duì)象。由于它們是各自新建的兩個(gè)Person對(duì)象;因此,返回false。
五、hashCode() 和 equals()的關(guān)系
我們以“類的用途”來(lái)將“hashCode() 和 equals()的關(guān)系”分2種情況來(lái)說(shuō)明。
1、第一種 不會(huì)創(chuàng)建“類對(duì)應(yīng)的散列表”
這里所說(shuō)的“不會(huì)創(chuàng)建類對(duì)應(yīng)的散列表”是說(shuō):我們不會(huì)在HashSet, Hashtable, HashMap等等這些本質(zhì)是散列表的數(shù)據(jù)結(jié)構(gòu)中,用到該類。例如,不會(huì)創(chuàng)建該類的HashSet集合。
在這種情況下,該類的“hashCode() 和 equals() ”沒(méi)有半毛錢關(guān)系的!這種情況下,equals() 用來(lái)比較該類的兩個(gè)對(duì)象是否相等。而hashCode() 則根本沒(méi)有任何作用,所以,不用理會(huì)hashCode()。
下面,我們通過(guò)示例查看類的兩個(gè)對(duì)象相等 以及 不等時(shí)hashCode()的取值。
代碼如下:
package com.demo;
/**
?* @desc 比較equals() 返回true 以及 返回false時(shí), hashCode()的值。
?* @author lixiaoxi
?*
?*/
public?class?NormalHashCodeTest?{
????
??????/**
?????* @desc Person類。
?????*/
????private?static?class?Person?{
????????int?age;
????????String name;
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?name + " - "?+age;
????????}
????????/**
?????????* @desc 覆蓋equals方法
?????????*/??
????????public?boolean equals(Object obj){
????????????if(obj == null){
????????????????return?false;
????????????}
??????????????
????????????//如果是同一個(gè)對(duì)象返回true,反之返回false
????????????if(this?== obj){
????????????????return?true;
????????????}
??????????????
????????????//判斷是否類型相同
????????????if(this.getClass() != obj.getClass()){
????????????????return?false;
????????????}
??????????????
????????????Person person = (Person)obj;
????????????return?name.equals(person.name) && age==person.age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建2個(gè)相同內(nèi)容的Person對(duì)象,
????????// 再用equals比較它們是否相等
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????Person p3 = new?Person("aaa", 200);
????????System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
????????System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
????}
}運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(30961619) p2(521452)
p1.equals(p3) : false; p1(30961619) p3(29744585)從結(jié)果也可以看出:p1和p2相等的情況下,hashCode()也不一定相等。
2、第二種 會(huì)創(chuàng)建“類對(duì)應(yīng)的散列表”
這里所說(shuō)的“會(huì)創(chuàng)建類對(duì)應(yīng)的散列表”是說(shuō):我們會(huì)在HashSet, Hashtable, HashMap等等這些本質(zhì)是散列表的數(shù)據(jù)結(jié)構(gòu)中,用到該類。例如,會(huì)創(chuàng)建該類的HashSet集合。
在這種情況下,該類的“hashCode() 和 equals() ”是有關(guān)系的:
1)、如果兩個(gè)對(duì)象相等,那么它們的hashCode()值一定相同。這里的相等是指,通過(guò)equals()比較兩個(gè)對(duì)象時(shí)返回true。
2)、如果兩個(gè)對(duì)象hashCode()相等,它們并不一定相等。
因?yàn)樵谏⒘斜碇校琱ashCode()相等,即兩個(gè)鍵值對(duì)的哈希值相等。然而哈希值相等,并不一定能得出鍵值對(duì)相等。補(bǔ)充說(shuō)一句:“兩個(gè)不同的鍵值對(duì),哈希值相等”,這就是哈希沖突。
此外,在這種情況下。若要判斷兩個(gè)對(duì)象是否相等,除了要覆蓋equals()之外,也要覆蓋hashCode()函數(shù)。否則,equals()無(wú)效。例如,創(chuàng)建Person類的HashSet集合,必須同時(shí)覆蓋Person類的equals() 和 hashCode()方法。如果單單只是覆蓋equals()方法。我們會(huì)發(fā)現(xiàn),equals()方法沒(méi)有達(dá)到我們想要的效果。
代碼如下:
package com.demo;
import java.util.HashSet;
/**
?* @desc 比較equals() 返回true 以及 返回false時(shí), hashCode()的值。
?* @author lixiaoxi
?*
?*/
public?class?ConflictHashCodeTest1?{
????
?????/**
?????* @desc Person類。
?????*/
????private?static?class?Person?{
????????int?age;
????????String name;
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?"("+name + ", "?+age+")";
????????}
????????/**
?????????* @desc 覆蓋equals方法
?????????*/??
????????@Override
????????public?boolean equals(Object obj){
????????????if(obj == null){
????????????????return?false;
????????????}
??????????????
????????????//如果是同一個(gè)對(duì)象返回true,反之返回false
????????????if(this?== obj){
????????????????return?true;
????????????}
??????????????
????????????//判斷是否類型相同
????????????if(this.getClass() != obj.getClass()){
????????????????return?false;
????????????}
??????????????
????????????Person person = (Person)obj;
????????????return?name.equals(person.name) && age==person.age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建Person對(duì)象,
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????Person p3 = new?Person("aaa", 200);
????????// 新建HashSet對(duì)象
????????HashSet set?= new?HashSet();
????????set.add(p1);
????????set.add(p2);
????????set.add(p3);
????????// 比較p1 和 p2, 并打印它們的hashCode()
????????System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
????????// 打印set
????????System.out.printf("set:%s\n", set);
????}
????
}運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(13905160) p2(30961619)
set:[(aaa, 200), (eee, 100), (eee, 100)]結(jié)果分析:
我們重寫了Person的equals()。但是,很奇怪的發(fā)現(xiàn):HashSet中仍然有重復(fù)元素:p1 和 p2。為什么會(huì)出現(xiàn)這種情況呢?這是因?yàn)殡m然p1 和 p2的內(nèi)容相等,但是它們的hashCode()不等;所以,HashSet在添加p1和p2的時(shí)候,認(rèn)為它們不相等。
下面,我們同時(shí)覆蓋equals() 和 hashCode()方法。
代碼如下:
package com.demo;
import java.util.HashSet;
/**
?* @desc 比較equals() 返回true 以及 返回false時(shí), hashCode()的值。
?* @author lixiaoxi
?*
?*/
public?class?ConflictHashCodeTest2?{
????
????/**
?????* @desc Person類。
?????*/
????private?static?class?Person?{
????????int?age;
????????String name;
????????public?Person(String name, int?age) {
????????????this.name = name;
????????????this.age = age;
????????}
????????public?String toString() {
????????????return?name + " - "?+age;
????????}
????????/**
?????????* @desc重寫hashCode
?????????*/??
????????@Override
????????public?int?hashCode(){
????????????int?nameHash = name.toUpperCase().hashCode();
????????????return?nameHash ^ age;
????????}
????????/**
?????????* @desc 覆蓋equals方法
?????????*/??
????????@Override
????????public?boolean equals(Object obj){
????????????if(obj == null){
????????????????return?false;
????????????}
??????????????
????????????//如果是同一個(gè)對(duì)象返回true,反之返回false
????????????if(this?== obj){
????????????????return?true;
????????????}
??????????????
????????????//判斷是否類型相同
????????????if(this.getClass() != obj.getClass()){
????????????????return?false;
????????????}
??????????????
????????????Person person = (Person)obj;
????????????return?name.equals(person.name) && age==person.age;
????????}
????}
????
????public?static?void?main(String[] args) {
????????// 新建Person對(duì)象,
????????Person p1 = new?Person("eee", 100);
????????Person p2 = new?Person("eee", 100);
????????Person p3 = new?Person("aaa", 200);
????????Person p4 = new?Person("EEE", 100);
????????// 新建HashSet對(duì)象
????????HashSet set?= new?HashSet();
????????set.add(p1);
????????set.add(p2);
????????set.add(p3);
????????// 比較p1 和 p2, 并打印它們的hashCode()
????????System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
????????// 比較p1 和 p4, 并打印它們的hashCode()
????????System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
????????// 打印set
????????System.out.printf("set:%s\n", set);
????}
}運(yùn)行結(jié)果:
p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]結(jié)果分析:
這下,equals()生效了,HashSet中沒(méi)有重復(fù)元素。
比較p1和p2,我們發(fā)現(xiàn):它們的hashCode()相等,通過(guò)equals()比較它們也返回true。所以,p1和p2被視為相等。
比較p1和p4,我們發(fā)現(xiàn):雖然它們的hashCode()相等;但是,通過(guò)equals()比較它們返回false。所以,p1和p4被視為不相等。
原文鏈接:cnblogs.com/xiaoxi/p/6428432.html
