- 首先equals()和hashcode()這兩個(gè)方法都是從object類中繼承過(guò)來(lái)的。
equals()方法在object類中定義如下:
public boolean equals(Object obj) {
return (this == obj);
}
很明顯是對(duì)兩個(gè)對(duì)象的地址值進(jìn)行的比較(即比較引用是否相同)文留。但是我們必需清楚胜臊,當(dāng)String 该押、Math问麸、還有Integer往衷、Double。严卖。席舍。。等這些封裝類在使用equals()方法時(shí)妄田,已經(jīng)覆蓋了object類的equals()方法。
比如在String類中如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
很明顯驮捍,這是進(jìn)行的內(nèi)容比較疟呐,而已經(jīng)不再是地址的比較。依次類推Double东且、Integer启具、Math。珊泳。鲁冯。。等等這些類都是重寫了equals()方法的色查,從而進(jìn)行的是內(nèi)容的比較薯演。當(dāng)然了基本類型是進(jìn)行值的比較,這個(gè)沒(méi)有什么好說(shuō)的秧了。
我們還應(yīng)該注意跨扮,Java語(yǔ)言對(duì)equals()的要求如下,這些要求是必須遵循的:
? 對(duì)稱性:如果x.equals(y)返回是“true”验毡,那么y.equals(x)也應(yīng)該返回是“true”衡创。
? 反射性:x.equals(x)必須返回是“true”。
? 類推性:如果x.equals(y)返回是“true”晶通,而且y.equals(z)返回是“true”璃氢,那么z.equals(x)也應(yīng)該返回是“true”。
? 還有一致性:如果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”尊剔。
以上這五點(diǎn)是重寫equals()方法時(shí)爪幻,必須遵守的準(zhǔn)則,如果違反會(huì)出現(xiàn)意想不到的結(jié)果须误,請(qǐng)大家一定要遵守挨稿。
- 其次是hashcode() 方法,在object類中定義如下:
public native int hashCode();
說(shuō)明是一個(gè)本地方法京痢,它的實(shí)現(xiàn)是根據(jù)本地機(jī)器相關(guān)的奶甘。當(dāng)然我們可以在自己寫的類中覆蓋hashcode()方法,比如String祭椰、Integer臭家、Double。方淤。钉赁。逢防。等等這些類都是覆蓋了hashcode()方法的毛雇。
例如在String類中定義的hashcode()方法如下:
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
解釋一下這個(gè)程序(String的API中寫到):
s[0]31^(n-1) + s[1]31^(n-2) + ... + s[n-1]
使用 int 算法轮锥,這里 s[i] 是字符串的第 i 個(gè)字符试吁,n 是字符串的長(zhǎng)度肴茄,^ 表示求冪勉躺。(空字符串的哈希碼為 0瘫怜。)
3.這里我們首先要明白一個(gè)問(wèn)題:
equals()相等的兩個(gè)對(duì)象扇售,hashcode()一定相等鸳谜;
equals()不相等的兩個(gè)對(duì)象膝藕,卻并不能證明他們的hashcode()不相等。換句話說(shuō)咐扭,equals()方法不相等的兩個(gè)對(duì)象束莫,hashcode()有可能相等。(我的理解是由于哈希碼在生成的時(shí)候產(chǎn)生沖突造成的)草描。
反過(guò)來(lái):hashcode()不等览绿,一定能推出equals()也不等;hashcode()相等穗慕,equals()可能相等饿敲,也可能不等。解釋下第3點(diǎn)的使用范圍逛绵,我的理解是在object怀各、String等類中都能使用倔韭。在object類中,hashcode()方法是本地方法瓢对,返回的是對(duì)象的地址值寿酌,而object類中的equals()方法比較的也是兩個(gè)對(duì)象的地址值,如果equals()相等硕蛹,說(shuō)明兩個(gè)對(duì)象地址值也相等醇疼,當(dāng)然hashcode()也就相等了;在String類中法焰,equals()返回的是兩個(gè)對(duì)象內(nèi)容的比較秧荆,當(dāng)兩個(gè)對(duì)象內(nèi)容相等時(shí),
Hashcode()方法根據(jù)String類的重寫(第2點(diǎn)里面已經(jīng)分析了)代碼的分析埃仪,也可知道hashcode()返回結(jié)果也會(huì)相等乙濒。以此類推,可以知道Integer卵蛉、Double等封裝類中經(jīng)過(guò)重寫的equals()和hashcode()方法也同樣適合于這個(gè)原則颁股。當(dāng)然沒(méi)有經(jīng)過(guò)重寫的類,在繼承了object類的equals()和hashcode()方法后傻丝,也會(huì)遵守這個(gè)原則甘有。
4.談到hashcode()和equals()就不能不說(shuō)到hashset,hashmap,hashtable中的使用,具體是怎樣呢桑滩,請(qǐng)看如下分析:
Hashset是繼承Set接口梧疲,Set接口又實(shí)現(xiàn)Collection接口允睹,這是層次關(guān)系运准。那么hashset是根據(jù)什么原理來(lái)存取對(duì)象的呢?
在hashset中不允許出現(xiàn)重復(fù)對(duì)象缭受,元素的位置也是不確定的胁澳。在hashset中又是怎樣判定元素是否重復(fù)的呢?這就是問(wèn)題的關(guān)鍵所在米者,經(jīng)過(guò)一下午的查詢求證終于獲得了一點(diǎn)啟示韭畸,和大家分享一下,在java的集合中蔓搞,判斷兩個(gè)對(duì)象是否相等的規(guī)則是:
1)胰丁,判斷兩個(gè)對(duì)象的hashCode是否相等
如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等喂分,完畢
如果相等锦庸,轉(zhuǎn)入2)
(這一點(diǎn)只是為了提高存儲(chǔ)效率而要求的,其實(shí)理論上沒(méi)有也可以蒲祈,但如果沒(méi)有甘萧,實(shí)際使用時(shí)效率會(huì)大大降低萝嘁,所以我們這里將其做為必需的。后面會(huì)重點(diǎn)講到這個(gè)問(wèn)題扬卷。)
2)牙言,判斷兩個(gè)對(duì)象用equals運(yùn)算是否相等
如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等
如果相等怪得,認(rèn)為兩個(gè)對(duì)象相等(equals()是判斷兩個(gè)對(duì)象是否相等的關(guān)鍵)
為什么是兩條準(zhǔn)則咱枉,難道用第一條不行嗎?不行汇恤,因?yàn)榍懊嬉呀?jīng)說(shuō)了庞钢,hashcode()相等時(shí),equals()方法也可能不等因谎,所以必須用第2條準(zhǔn)則進(jìn)行限制基括,才能保證加入的為非重復(fù)元素。
比如下面的代碼:
public static void main(String args[]){
String s1=new String("zhaoxudong");
String s2=new String("zhaoxudong");
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true
System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode()
System.out.println(s2.hashCode());
Set hashset=new HashSet();
hashset.add(s1);
hashset.add(s2);
/*實(shí)質(zhì)上在添加s1,s2時(shí)财岔,運(yùn)用上面說(shuō)到的兩點(diǎn)準(zhǔn)則风皿,可以知道
hashset認(rèn)為s1和s2是相等的,是在添加重復(fù)元素匠璧,所以讓s2覆蓋了s1;*/
Iterator it=hashset.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
最后在while循環(huán)的時(shí)候只打印出了一個(gè)”zhaoxudong”桐款。
輸出結(jié)果為:false
true
-967303459
-967303459
這是因?yàn)镾tring類已經(jīng)重寫了equals()方法和hashcode()方法,所以在根據(jù)上面的第1.2條原則判定時(shí)夷恍,hashset認(rèn)為它們是相等的對(duì)象魔眨,進(jìn)行了重復(fù)添加。
但是看下面的程序:
import java.util.*;
public class HashSetTest
{
public static void main(String[] args)
{
HashSet hs=new HashSet();
hs.add(new Student(1,"zhangsan"));
hs.add(new Student(2,"lisi"));
hs.add(new Student(3,"wangwu"));
hs.add(new Student(1,"zhangsan"));
Iterator it=hs.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
class Student
{
int num;
String name;
Student(int num,String name)
{
this.num=num;
this.name=name;
}
public String toString()
{
return num+":"+name;
}
}
輸出結(jié)果為:
1:zhangsan
1:zhangsan
3:wangwu
2:lisi
問(wèn)題出現(xiàn)了酿雪,為什么hashset添加了相等的元素呢遏暴,這是不是和hashset的原則違背了呢?回答是:沒(méi)有
因?yàn)樵诟鶕?jù)hashcode()對(duì)兩次建立的new Student(1,"zhangsan")對(duì)象進(jìn)行比較時(shí)指黎,生成的是不同的哈希碼值朋凉,所以hashset把他當(dāng)作不同的對(duì)象對(duì)待了,當(dāng)然此時(shí)的equals()方法返回的值也不等(這個(gè)不用解釋了吧)醋安。那么為什么會(huì)生成不同的哈希碼值呢杂彭?上面我們?cè)诒容^s1和s2的時(shí)候不是生成了同樣的哈希碼嗎?原因就在于我們自己寫的Student類并沒(méi)有重新自己的hashcode()和equals()方法吓揪,所以在比較時(shí)亲怠,是繼承的object類中的hashcode()方法,呵呵柠辞,各位還記得object類中的hashcode()方法比較的是什么吧M呕唷!
它是一個(gè)本地方法,比較的是對(duì)象的地址(引用地址)徙垫,使用new方法創(chuàng)建對(duì)象讥裤,兩次生成的當(dāng)然是不同的對(duì)象了(這個(gè)大家都能理解吧。姻报。己英。),造成的結(jié)果就是兩個(gè)對(duì)象的hashcode()返回的值不一樣吴旋。所以根據(jù)第一個(gè)準(zhǔn)則损肛,hashset會(huì)把它們當(dāng)作不同的對(duì)象對(duì)待,自然也用不著第二個(gè)準(zhǔn)則進(jìn)行判定了荣瑟。那么怎么解決這個(gè)問(wèn)題呢治拿??
答案是:在Student類中重新hashcode()和equals()方法笆焰。
例如:
class Student
{
int num;
String name;
Student(int num,String name)
{
this.num=num;
this.name=name;
}
public int hashCode()
{
return num*name.hashCode();
}
public boolean equals(Object o)
{
Student s=(Student)o;
return num==s.num && name.equals(s.name);
}
public String toString()
{
return num+":"+name;
}
}
根據(jù)重寫的方法劫谅,即便兩次調(diào)用了new Student(1,"zhangsan"),我們?cè)讷@得對(duì)象的哈希碼時(shí)嚷掠,根據(jù)重寫的方法hashcode()捏检,獲得的哈希碼肯定是一樣的(這一點(diǎn)應(yīng)該沒(méi)有疑問(wèn)吧)。
當(dāng)然根據(jù)equals()方法我們也可判斷是相同的不皆。所以在向hashset集合中添加時(shí)把它們當(dāng)作重復(fù)元素看待了贯城。所以運(yùn)行修改后的程序時(shí),我們會(huì)發(fā)現(xiàn)運(yùn)行結(jié)果是:
1:zhangsan
3:wangwu
2:lisi
可以看到重復(fù)元素的問(wèn)題已經(jīng)消除霹娄。
關(guān)于在hibernate的pojo類中能犯,重新equals()和hashcode()的問(wèn)題:
1),重點(diǎn)是equals犬耻,重寫hashCode只是技術(shù)要求(為了提高效率)
2)踩晶,為什么要重寫equals呢,因?yàn)樵趈ava的集合框架中香追,是通過(guò)equals來(lái)判斷兩個(gè)對(duì)象是否相等的
3)合瓢,在hibernate中坦胶,經(jīng)常使用set集合來(lái)保存相關(guān)對(duì)象透典,而set集合是不允許重復(fù)的。我們?cè)賮?lái)談?wù)勄懊嫣岬皆谙騢ashset集合中添加元素時(shí),怎樣判斷對(duì)象是否相同的準(zhǔn)則顿苇,前面說(shuō)了兩條峭咒,其實(shí)只要重寫equals()這一條也可以。
但當(dāng)hashset中元素比較多時(shí)纪岁,或者是重寫的equals()方法比較復(fù)雜時(shí)凑队,我們只用equals()方法進(jìn)行比較判斷,效率也會(huì)非常低,所以引入了hashcode()這個(gè)方法漩氨,只是為了提高效率西壮,但是我覺得這是非常有必要的(所以我們?cè)谇懊嬉詢蓷l準(zhǔn)則來(lái)進(jìn)行hashset的元素是否重復(fù)的判斷)。
比如可以這樣寫:
public int hashCode(){
return 1;}//等價(jià)于hashcode無(wú)效
這樣做的效果就是在比較哈希碼的時(shí)候不能進(jìn)行判斷叫惊,因?yàn)槊總€(gè)對(duì)象返回的哈希碼都是1款青,每次都必須要經(jīng)過(guò)比較equals()方法后才能進(jìn)行判斷是否重復(fù),這當(dāng)然會(huì)引起效率的大大降低霍狰。
我有一個(gè)問(wèn)題抡草,如果像前面提到的在hashset中判斷元素是否重復(fù)的必要方法是equals()方法(根據(jù)網(wǎng)上找到的觀點(diǎn)),但是這里并沒(méi)有涉及到關(guān)于哈希表的問(wèn)題蔗坯,可是這個(gè)集合卻叫hashset康震,這是為什么?宾濒?
我想腿短,在hashmap,hashtable中的存儲(chǔ)操作,依然遵守上面的準(zhǔn)則绘梦。所以這里不再多說(shuō)答姥。這些是今天看書,網(wǎng)上查詢資料谚咬,自己總結(jié)出來(lái)的鹦付,部分代碼和語(yǔ)言是引述,但是千真萬(wàn)確是自己總結(jié)出來(lái)的择卦。有錯(cuò)誤之處和不詳細(xì)不清楚的地方還請(qǐng)大家指出敲长,我也是初學(xué)者,所以難免會(huì)有錯(cuò)誤的地方秉继,希望大家共同討論祈噪。