Java:重写equals()和hashCode()
http://blog.csdn.net/ansel13/article/details/5437486
很基礎的東西就是由于沒上心,三番五次地出錯,這次好好總結下吧。?
眾所周之,String 、Math、還有Integer、Double。。。。等這些封裝類重寫了Object中的equals()方法,讓它不再比較句柄(引用),而是比較對象中實際包含的整數的值,即比較的是內容。
而Object的equals()方法比較的是地址值。?
要是放在list中,并且list需要排序,要為類重寫compareTo方法
要是放入Hashtable、HashMap、HashSet、LinkedHashMap中,要重寫equals和hashCode
(待解決問題:1. 如果類作為值得話,需要重載哪些? 2. 對于優先隊列要重載哪些)
一般來說,如果你要把一個類的對象放入容器中,那么通常要為其重寫equals()方法,讓他們比較地址值而不是內容值。特別地,如果要把你的類的對象放入散列中,那么還要重寫hashCode()方法;要放到有序容器中,還要重寫compareTo()方法。?
本人愚見,只有用到Hashtable、HashMap、HashSet、LinkedHashMap等時才要注意hashcode,其他地方hashcode無用。?
為什么要重寫hashCode方法??
我們應該先了解java判斷兩個對象是否相等的規則。 在java的集合中,判斷兩個對象是否相等的規則是:?
首先,判斷兩個對象的hashCode是否相等?
如果不相等,認為兩個對象也不相等?
如果相等,則判斷兩個對象用equals運算是否相等?
如果不相等,認為兩個對象也不相等?
如果相等,認為兩個對象相等?
我們在equals方法中需要向下轉型,效率很低,所以先判斷hashCode方法可以提高效率。
---------------------------------------------------------- 轉載----------------------------------------------------------?
今天下午研究了半天hashcode()和equals()方法,終于有了一點點的明白,寫下來與大家分享(zhaoxudong 2008.10.23晚21.36)。?
1. 首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。?
equals()方法在object類中定義如下:?
public boolean equals(Object obj) {?
return (this == obj);?
}?
很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。但是我們必需清楚,當String 、Math、還有Integer、Double。。。。等這些封裝類在使用equals()方法時,已經覆蓋了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;?
}?
很明顯,這是進行的內容比較,而已經不再是地址的比較。依次類推Double、Integer、Math。。。。等等這些類都是重寫了equals()方法的,從而進行的是內容的比較。當然了基本類型是進行值的比較,這個沒有什么好說的。?
我們還應該注意,Java語言對equals()的要求如下,這些要求是必須遵循的:?
? 對稱性:如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是“true”。?
? 反射性:x.equals(x)必須返回是“true”。?
? 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也應該返回是“true”。?
? 還有一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重復x.equals(y)多少次,返回都是“true”。?
? 任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。?
以上這五點是重寫equals()方法時,必須遵守的準則,如果違反會出現意想不到的結果,請大家一定要遵守。?
2. 其次是hashcode() 方法,在object類中定義如下:?
public native int hashCode();?
說明是一個本地方法,它的實現是根據本地機器相關的。當然我們可以在自己寫的類中覆蓋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;?
}?
解釋一下這個程序(String的API中寫到):?
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]?
使用 int 算法,這里 s[i] 是字符串的第 i 個字符,n 是字符串的長度,^ 表示求冪。(空字符串的哈希碼為 0。)?
3.這里我們首先要明白一個問題:?
equals()相等的兩個對象,hashcode()一定相等;?
equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等。(我的理解是由于哈希碼在生成的時候產生沖突造成的)。?
反過來:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。解釋下第3點的使用范圍,我的理解是在object、String等類中都能使用。在object類中,hashcode()方法是本地方法,返回的是對象的地址值,而object類中的equals()方法比較的也是兩個對象的地址值,如果equals()相等,說明兩個對象地址值也相等,當然hashcode()也就相等了;在String類中,equals()返回的是兩個對象內容的比較,當兩個對象內容相等時,?
Hashcode()方法根據String類的重寫(第2點里面已經分析了)代碼的分析,也可知道hashcode()返回結果也會相等。以此類推,可以知道Integer、Double等封裝類中經過重寫的equals()和hashcode()方法也同樣適合于這個原則。當然沒有經過重寫的類,在繼承了object類的equals()和hashcode()方法后,也會遵守這個原則。?
4.談到hashcode()和equals()就不能不說到hashset,hashmap,hashtable中的使用,具體是怎樣呢,請看如下分析:?
Hashset是繼承Set接口,Set接口又實現Collection接口,這是層次關系。那么hashset是根據什么原理來存取對象的呢??
在hashset中不允許出現重復對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重復的呢?這就是問題的關鍵所在,經過一下午的查詢求證終于獲得了一點啟示,和大家分享一下,在java的集合中,判斷兩個對象是否相等的規則是:?
1),判斷兩個對象的hashCode是否相等?
如果不相等,認為兩個對象也不相等,完畢?
如果相等,轉入2)?
(這一點只是為了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這里將其做為必需的。后面會重點講到這個問題。)?
2),判斷兩個對象用equals運算是否相等?
如果不相等,認為兩個對象也不相等?
如果相等,認為兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)?
為什么是兩條準則,難道用第一條不行嗎?不行,因為前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的為非重復元素。?
比如下面的代碼:?
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);?
Iterator it=hashset.iterator();?
while(it.hasNext())?
{?
System.out.println(it.next());?
}?
最后在while循環的時候只打印出了一個”zhaoxudong”。?
輸出結果為:false?
true?
-967303459?
-967303459?
這是因為String類已經重寫了equals()方法和hashcode()方法,所以在根據上面的第1.2條原則判定時,hashset認為它們是相等的對象,進行了重復添加。?
但是看下面的程序:?
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;?
}?
}?
輸出結果為:?
1:zhangsan?
1:zhangsan?
3:wangwu?
2:lisi?
問題出現了,為什么hashset添加了相等的元素呢,這是不是和hashset的原則違背了呢?回答是:沒有?
因為在根據hashcode()對兩次建立的new Student(1,"zhangsan")對象進行比較時,生成的是不同的哈希碼值,所以hashset把他當作不同的對象對待了,當然此時的equals()方法返回的值也不等(這個不用解釋了吧)。那么為什么會生成不同的哈希碼值呢?上面我們在比較s1和s2的時候不是生成了同樣的哈希碼嗎?原因就在于我們自己寫的Student類并沒有重新自己的hashcode()和equals()方法,所以在比較時,是繼承的object類中的hashcode()方法,呵呵,各位還記得object類中的hashcode()方法比較的是什么吧!!?
它是一個本地方法,比較的是對象的地址(引用地址),使用new方法創建對象,兩次生成的當然是不同的對象了(這個大家都能理解吧。。。),造成的結果就是兩個對象的hashcode()返回的值不一樣。所以根據第一個準則,hashset會把它們當作不同的對象對待,自然也用不著第二個準則進行判定了。那么怎么解決這個問題呢??
答案是:在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;?
}?
}?
根據重寫的方法,即便兩次調用了new Student(1,"zhangsan"),我們在獲得對象的哈希碼時,根據重寫的方法hashcode(),獲得的哈希碼肯定是一樣的(這一點應該沒有疑問吧)。?
當然根據equals()方法我們也可判斷是相同的。所以在向hashset集合中添加時把它們當作重復元素看待了。所以運行修改后的程序時,我們會發現運行結果是:?
1:zhangsan?
3:wangwu?
2:lisi?
可以看到重復元素的問題已經消除。?
關于在hibernate的pojo類中,重新equals()和hashcode()的問題:?
1),重點是equals,重寫hashCode只是技術要求(為了提高效率)?
2),為什么要重寫equals呢,因為在java的集合框架中,是通過equals來判斷兩個對象是否相等的?
3),在hibernate中,經常使用set集合來保存相關對象,而set集合是不允許重復的。我們再來談談前面提到在向hashset集合中添加元素時,怎樣判斷對象是否相同的準則,前面說了兩條,其實只要重寫equals()這一條也可以。
但當hashset中元素比較多時,或者是重寫的equals()方法比較復雜時,我們只用equals()方法進行比較判斷,效率也會非常低,所以引入了hashcode()這個方法,只是為了提高效率,但是我覺得這是非常有必要的(所以我們在前面以兩條準則來進行hashset的元素是否重復的判斷)。?
比如可以這樣寫:?
public int hashCode(){?
return 1;}//等價于hashcode無效?
這樣做的效果就是在比較哈希碼的時候不能進行判斷,因為每個對象返回的哈希碼都是1,每次都必須要經過比較equals()方法后才能進行判斷是否重復,這當然會引起效率的大大降低。?
我有一個問題,如果像前面提到的在hashset中判斷元素是否重復的必要方法是equals()方法(根據網上找到的觀點),但是這里并沒有涉及到關于哈希表的問題,可是這個集合卻叫hashset,這是為什么???
我想,在hashmap,hashtable中的存儲操作,依然遵守上面的準則。所以這里不再多說。這些是今天看書,網上查詢資料,自己總結出來的,部分代碼和語言是引述,但是千真萬確是自己總結出來的。有錯誤之處和不詳細不清楚的地方還請大家指出,我也是初學者,所以難免會有錯誤的地方,希望大家共同討論。
?
---------------------------------------------------------- 轉載----------------------------------------------------------
?
版權聲明:原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。http://zhangjunhd.blog.51cto.com/113473/71571
以下內容總結自《Effective Java》。
1.何時需要重寫equals()
當一個類有自己特有的“邏輯相等”概念(不同于對象身份的概念)。
2.設計equals()
[1]使用instanceof操作符檢查“實參是否為正確的類型”。
[2]對于類中的每一個“關鍵域”,檢查實參中的域與當前對象中對應的域值。
[2.1]對于非float和double類型的原語類型域,使用==比較;
[2.2]對于對象引用域,遞歸調用equals方法;
[2.3]對于float域,使用Float.floatToIntBits(afloat)轉換為int,再使用==比較;
[2.4]對于double域,使用Double.doubleToLongBits(adouble) 轉換為int,再使用==比較;
[2.5]對于數組域,調用Arrays.equals方法。
3.當改寫equals()的時候,總是要改寫hashCode()
根據一個類的equals方法(改寫后),兩個截然不同的實例有可能在邏輯上是相等的,但是,根據Object.hashCode方法,它們僅僅是兩個對象。因此,違反了“相等的對象必須具有相等的散列碼”。
4.設計hashCode()
[1]把某個非零常數值,例如17,保存在int變量result中;
[2]對于對象中每一個關鍵域f(指equals方法中考慮的每一個域):
[2.1]boolean型,計算(f ? 0 : 1);
[2.2]byte,char,short型,計算(int);
[2.3]long型,計算(int) (f ^ (f>>>32));
[2.4]float型,計算Float.floatToIntBits(afloat);
[2.5]double型,計算Double.doubleToLongBits(adouble)得到一個long,再執行[2.3];
[2.6]對象引用,遞歸調用它的hashCode方法;
[2.7]數組域,對其中每個元素調用它的hashCode方法。
[3]將上面計算得到的散列碼保存到int變量c,然后執行 result=37*result+c;
[4]返回result。
5.示例
下面的這個類遵循上面的設計原則,重寫了類的equals()和hashCode()。
(主要重寫equals傳入的參數一定是object的,只有和父類中方法的參數類型也一致,才能做到重寫)
重寫equals方法的要點:?
1. 使用==操作符檢查“實參是否為指向對象的一個引用”。?
2. 使用instanceof操作符檢查“實參是否為正確的類型”。?
3. 把實參轉換到正確的類型。?
4. 對于該類中每一個“關鍵”域,檢查實參中的域與當前對象中對應的域值是否匹?
配。對于既不是float也不是double類型的基本類型的域,可以使用==操作符?
進行比較;對于對象引用類型的域,可以遞歸地調用所引用的對象的equals方法;?
對于float類型的域,先使用Float.floatToIntBits轉換成int類型的值,?
然后使用==操作符比較int類型的值;對于double類型的域,先使用?
Double.doubleToLongBits轉換成long類型的值,然后使用==操作符比較?
long類型的值。?
5. 當你編寫完成了equals方法之后,應該問自己三個問題:它是否是對稱的、傳?
遞的、一致的?(其他兩個特性通常會自行滿足)如果答案是否定的,那么請找到?
這些特性未能滿足的原因,再修改equals方法的代碼。
總結
以上是生活随笔為你收集整理的Java:重写equals()和hashCode()的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常见Java面试题 – 第三部分:重载(
- 下一篇: java实现多路分发