equals()與hashCode()方法詳解

java.lang.Object類中有兩個非常重要的方法:

1?public?boolean?equals(Object obj)

2?public?int?hashCode()?

Object類是類繼承結(jié)構(gòu)的基礎(chǔ),所以是每一個類的父類。所有的對象,包括數(shù)組,都實現(xiàn)了在Object類中定義的方法。

equals()方法詳解

equals()方法是用來判斷其他的對象是否和該對象相等.

? equals()方法在object類中定義如下:?

publicboolean equals(Object obj) {?

? ? return(this== obj);?

}?

很明顯是對兩個對象的地址值進(jìn)行的比較(即比較引用是否相同)。但是我們知道,String 、Math、Integer、Double等這些封裝類在使用equals()方法時,已經(jīng)覆蓋了object類的equals()方法。

? 比如在String類中如下:

publicboolean equals(Object anObject) {?

? ? if(this== anObject) {?

? ? ? ? returntrue;?

? ? }?

? ? if(anObjectinstanceof String) {?

? ? ? ? String anotherString = (String)anObject;?

? ? ? ? intn = count;?

? ? ? ? if(n == anotherString.count) {?

? ? ? ? ? ? charv1[] = value;?

? ? ? ? ? ? charv2[] = anotherString.value;?

? ? ? ? ? ? inti = offset;?

? ? ? ? ? ? intj = anotherString.offset;?

? ? ? ? ? ? while(n– != 0) {?

? ? ? ? ? ? ? ? if(v1[i++] != v2[j++])?

? ? ? ? ? ? ? ? ? ? returnfalse;?

? ? ? ? ? ? }?

? ? ? ? ? ? returntrue;?

? ? ? ? }?

? ? }?

? ? returnfalse;?

}?

很明顯,這是進(jìn)行的內(nèi)容比較,而已經(jīng)不再是地址的比較。依次類推Math、Integer、Double等這些類都是重寫了equals()方法的,從而進(jìn)行的是內(nèi)容的比較。當(dāng)然,基本類型是進(jìn)行值的比較。

它的性質(zhì)有:

自反性(reflexive)。對于任意不為null的引用值x,x.equals(x)一定是true。

對稱性(symmetric)。對于任意不為null的引用值x和y,當(dāng)且僅當(dāng)x.equals(y)是true時,y.equals(x)也是true。

傳遞性(transitive)。對于任意不為null的引用值x、y和z,如果x.equals(y)是true,同時y.equals(z)是true,那么x.equals(z)一定是true。

一致性(consistent)。對于任意不為null的引用值x和y,如果用于equals比較的對象信息沒有被修改的話,多次調(diào)用時x.equals(y)要么一致地返回true要么一致地返回false。

對于任意不為null的引用值x,x.equals(null)返回false。

對于Object類來說,equals()方法在對象上實現(xiàn)的是差別可能性最大的等價關(guān)系,即,對于任意非null的引用值x和y,當(dāng)且僅當(dāng)x和y引用的是同一個對象,該方法才會返回true。

需要注意的是當(dāng)equals()方法被override時,hashCode()也要被override。按照一般hashCode()方法的實現(xiàn)來說,相等的對象,它們的hash code一定相等。

hashcode() 方法詳解

hashCode()方法給對象返回一個hash code值。這個方法被用于hash tables,例如HashMap。

它的性質(zhì)是:

在一個Java應(yīng)用的執(zhí)行期間,如果一個對象提供給equals做比較的信息沒有被修改的話,該對象多次調(diào)用hashCode()方法,該方法必須始終如一返回同一個integer。

如果兩個對象根據(jù)equals(Object)方法是相等的,那么調(diào)用二者各自的hashCode()方法必須產(chǎn)生同一個integer結(jié)果。

并不要求根據(jù)equals(java.lang.Object)方法不相等的兩個對象,調(diào)用二者各自的hashCode()方法必須產(chǎn)生不同的integer結(jié)果。然而,程序員應(yīng)該意識到對于不同的對象產(chǎn)生不同的integer結(jié)果,有可能會提高h(yuǎn)ash table的性能。

大量的實踐表明,由Object類定義的hashCode()方法對于不同的對象返回不同的integer。

在object類中,hashCode定義如下:

publicnativeinthashCode();

?說明是一個本地方法,它的實現(xiàn)是根據(jù)本地機(jī)器相關(guān)的。當(dāng)然我們可以在自己寫的類中覆蓋hashcode()方法,比如String、Integer、Double等這些類都是覆蓋了hashcode()方法的。例如在String類中定義的hashcode()方法如下:

publicint hashCode() {?

? ? inth = hash;?

? ? if(h == 0) {?

? ? ? ? intoff = offset;?

? ? ? ? charval[] = value;?

? ? ? ? intlen = count;?

? ? ? ? for(inti = 0; i < len; i++) {?

? ? ? ? ? ? h = 31 * h + val[off++];?

? ? ? ? }?

? ? ? ? hash = h;?

? ? }?

? ? return h;?

}?

解釋一下這個程序(String的API中寫到):s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]

使用 int 算法,這里 s[i] 是字符串的第 i 個字符,n 是字符串的長度,^ 表示求冪(空字符串的哈希碼為 0)。

想要弄明白hashCode的作用,必須要先知道Java中的集合。

總的來說,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內(nèi)的元素是有序的,元素可以重復(fù);后者元素?zé)o序,但元素不可重復(fù)。這里就引出一個問題:要想保證元素不重復(fù),可兩個元素是否重復(fù)應(yīng)該依據(jù)什么來判斷呢?

這就是Object.equals方法了。但是,如果每增加一個元素就檢查一次,那么當(dāng)元素很多時,后添加到集合中的元素比較的次數(shù)就非常多了。也就是說,如果集合中現(xiàn)在已經(jīng)有1000個元素,那么第1001個元素加入集合時,它就要調(diào)用1000次equals方法。這顯然會大大降低效率。

于是,Java采用了哈希表的原理。哈希(Hash)實際上是個人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也稱為散列算法,是將數(shù)據(jù)依特定算法直接指定到一個地址上,初學(xué)者可以簡單理解,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能并不是)。

這樣一來,當(dāng)集合要添加新的元素時,先調(diào)用這個元素的hashCode方法,就一下子能定位到它應(yīng)該放置的物理位置上。如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進(jìn)行任何比較了;如果這個位置上已經(jīng)有元素了,就調(diào)用它的equals方法與新元素進(jìn)行比較,相同的話就不存了,不相同就散列其它的地址。所以這里存在一個沖突解決的問題。這樣一來實際調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。

?簡而言之,在集合查找時,hashcode能大大降低對象比較次數(shù),提高查找效率!

Java對象的eqauls方法和hashCode方法是這樣規(guī)定的:

1、相等(相同)的對象必須具有相等的哈希碼(或者散列碼)。

2、如果兩個對象的hashCode相同,它們并不一定相同。

?以下是Object對象API關(guān)于equal方法和hashCode方法的說明:

If two objects are equal according to theequals(Object)method, then calling thehashCodemethod on each of the two objects must produce the same integer result.

It isnotrequired that if two objects are unequal according to theequals(java.lang.Object)method, then calling thehashCodemethod on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

以上API說明是對之前2點(diǎn)的官方詳細(xì)說明

關(guān)于第一點(diǎn),相等(相同)的對象必須具有相等的哈希碼(或者散列碼),為什么?

?想象一下,假如兩個Java對象A和B,A和B相等(eqauls結(jié)果為true),但A和B的哈希碼不同,則A和B存入HashMap時的哈希碼計算得到的HashMap內(nèi)部數(shù)組位置索引可能不同,那么A和B很有可能允許同時存入HashMap,顯然相等/相同的元素是不允許同時存入HashMap,HashMap不允許存放重復(fù)元素。

?關(guān)于第二點(diǎn),兩個對象的hashCode相同,它們并不一定相同

?也就是說,不同對象的hashCode可能相同;假如兩個Java對象A和B,A和B不相等(eqauls結(jié)果為false),但A和B的哈希碼相等,將A和B都存入HashMap時會發(fā)生哈希沖突,也就是A和B存放在HashMap內(nèi)部數(shù)組的位置索引相同這時HashMap會在該位置建立一個鏈接表,將A和B串起來放在該位置,顯然,該情況不違反HashMap的使用原則,是允許的。當(dāng)然,哈希沖突越少越好,盡量采用好的哈希算法以避免哈希沖突。

?所以,Java對于eqauls方法和hashCode方法是這樣規(guī)定的:? ? ?

?  1.如果兩個對象相同,那么它們的hashCode值一定要相同;

2.如果兩個對象的hashCode相同,它們并不一定相同(這里說的對象相同指的是用eqauls方法比較)。

如不按要求去做了,會發(fā)現(xiàn)相同的對象可以出現(xiàn)在Set集合中,同時,增加新元素的效率會大大下降。

3.equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。

??????? 換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等(我的理解是由于哈希碼在生成的時候產(chǎn)生沖突造成的)。反過來,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

在object類中,hashcode()方法是本地方法,返回的是對象的地址值,而object類中的equals()方法比較的也是兩個對象的地址值,如果equals()相等,說明兩個對象地址值也相等,當(dāng)然hashcode()也就相等了;在String類中,equals()返回的是兩個對象內(nèi)容的比較,當(dāng)兩個對象內(nèi)容相等時,Hashcode()方法根據(jù)String類的重寫代碼的分析,也可知道hashcode()返回結(jié)果也會相等。以此類推,可以知道Integer、Double等封裝類中經(jīng)過重寫的equals()和hashcode()方法也同樣適合于這個原則。當(dāng)然沒有經(jīng)過重寫的類,在繼承了object類的equals()和hashcode()方法后,也會遵守這個原則。

Hashset、Hashmap、Hashtable與hashcode()和equals()的密切關(guān)系

Hashset是繼承Set接口,Set接口又實現(xiàn)Collection接口,這是層次關(guān)系。那么Hashset、Hashmap、Hashtable中的存儲操作是根據(jù)什么原理來存取對象的呢?

下面以HashSet為例進(jìn)行分析,我們都知道:在hashset中不允許出現(xiàn)重復(fù)對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重復(fù)的呢?在java的集合中,判斷兩個對象是否相等的規(guī)則是:

1.判斷兩個對象的hashCode是否相等

如果不相等,認(rèn)為兩個對象也不相等,完畢

如果相等,轉(zhuǎn)入2

(這一點(diǎn)只是為了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這里將其做為必需的。)

2.判斷兩個對象用equals運(yùn)算是否相等

如果不相等,認(rèn)為兩個對象也不相等

如果相等,認(rèn)為兩個對象相等(equals()是判斷兩個對象是否相等的關(guān)鍵)

為什么是兩條準(zhǔn)則,難道用第一條不行嗎?不行,因為前面已經(jīng)說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準(zhǔn)則進(jìn)行限制,才能保證加入的為非重復(fù)元素。

例1:

1package com.bijian.study;

2 3import java.util.HashSet;?

4import java.util.Iterator;?

5import java.util.Set;?

?7publicclass HashSetTest { 8 9publicstaticvoid main(String args[]) {10String s1 =newString("aaa");11String s2 =newString("aaa");12System.out.println(s1 == s2);13? ? ? ? System.out.println(s1.equals(s2));14? ? ? ? System.out.println(s1.hashCode());15? ? ? ? System.out.println(s2.hashCode());16Set hashset =new HashSet();17? ? ? ? hashset.add(s1);18? ? ? ? hashset.add(s2);19Iterator it = hashset.iterator();20while (it.hasNext()) {21? ? ? ? ? ? System.out.println(it.next());22? ? ? ? }23? ? }24}

運(yùn)行結(jié)果:

falsetrue9632196321aaa

? 這是因為String類已經(jīng)重寫了equals()方法和hashcode()方法,所以hashset認(rèn)為它們是相等的對象,進(jìn)行了重復(fù)添加。

例2:

1package com.bijian.study; 2 3import java.util.HashSet; 4import java.util.Iterator; 5 6publicclass HashSetTest { 7 8publicstaticvoid main(String[] args) { 9HashSet hs =new HashSet();10hs.add(newStudent(1, "zhangsan"));11hs.add(newStudent(2, "lisi"));12hs.add(newStudent(3, "wangwu"));13hs.add(newStudent(1, "zhangsan"));1415Iterator it = hs.iterator();16while (it.hasNext()) {17? ? ? ? ? ? System.out.println(it.next());18? ? ? ? }19? ? }20}2122class Student {23int num;24? ? String name;2526Student(int num, String name) {27this.num = num;28this.name = name;29? ? }3031public String toString() {32returnnum + ":" + name;33? ? }34}

運(yùn)行結(jié)果:

1:zhangsan? 3:wangwu? 2:lisi? 1:zhangsan

為什么hashset添加了相等的元素呢,這是不是和hashset的原則違背了呢?回答是:沒有。因為在根據(jù)hashcode()對兩次建立的new Student(1,“zhangsan”)對象進(jìn)行比較時,生成的是不同的哈希碼值,所以hashset把他當(dāng)作不同的對象對待了,當(dāng)然此時的equals()方法返回的值也不等。

??????? 為什么會生成不同的哈希碼值呢?上面我們在比較s1和s2的時候不是生成了同樣的哈希碼嗎?原因就在于我們自己寫的Student類并沒有重新自己的hashcode()和equals()方法,所以在比較時,是繼承的object類中的hashcode()方法,而object類中的hashcode()方法是一個本地方法,比較的是對象的地址(引用地址),使用new方法創(chuàng)建對象,兩次生成的當(dāng)然是不同的對象了,造成的結(jié)果就是兩個對象的hashcode()返回的值不一樣,所以Hashset會把它們當(dāng)作不同的對象對待。

????????怎么解決這個問題呢?答案是:在Student類中重新hashcode()和equals()方法。

class Student {

? ? int num;

? ? String name;

? ? Student(int num, String name) {

? ? ? ? this.num = num;

? ? ? ? this.name = name;

? ? }

? ? publicint hashCode() {

? ? ? ? returnnum * name.hashCode();

? ? }

? ? publicboolean equals(Object o) {

? ? ? ? Student s = (Student) o;

? ? ? ? returnnum == s.num && name.equals(s.name);

? ? }

? ? public String toString() {

? ? ? ? returnnum + ":" + name;

? ? }

}

運(yùn)行結(jié)果:

1:zhangsan? 3:wangwu? 2:lisi

可以看到重復(fù)元素的問題已經(jīng)消除,根據(jù)重寫的方法,即便兩次調(diào)用了new Student(1,"zhangsan"),我們在獲得對象的哈希碼時,根據(jù)重寫的方法hashcode(),獲得的哈希碼肯定是一樣的,當(dāng)然根據(jù)equals()方法我們也可判斷是相同的,所以在向hashset集合中添加時把它們當(dāng)作重復(fù)元素看待了。

重寫equals()和hashcode()小結(jié):

1.重點(diǎn)是equals,重寫hashCode只是技術(shù)要求(為了提高效率)

2.為什么要重寫equals呢?因為在java的集合框架中,是通過equals來判斷兩個對象是否相等的

3.在hibernate中,經(jīng)常使用set集合來保存相關(guān)對象,而set集合是不允許重復(fù)的。在向HashSet集合中添加元素時,其實只要重寫equals()這一條也可以。但當(dāng)hashset中元素比較多時,或者是重寫的equals()方法比較復(fù)雜時,我們只用equals()方法進(jìn)行比較判斷,效率也會非常低,所以引入了hashCode()這個方法,只是為了提高效率,且這是非常有必要的。比如可以這樣寫:

publicint hashCode(){?

? return1;//等價于hashcode無效? }

這樣做的效果就是在比較哈希碼的時候不能進(jìn)行判斷,因為每個對象返回的哈希碼都是1,每次都必須要經(jīng)過比較equals()方法后才能進(jìn)行判斷是否重復(fù),這當(dāng)然會引起效率的大大降低。

http://www.cnblogs.com/Qian123/p/5703507.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評論 2 380

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