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)然會引起效率的大大降低。