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。)
首先,想要明白hashCode的作用,你必須要先知道Java中的集合。
總的來說,Java中的集合
(Collection)有兩類,一類是List,再有一類是Set。
你知道它們的區別嗎?前者集合內的元素是有序的,元素可以重複;後者元素無
序,但元素不可重複。
那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?
這就是
Object.equals方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後添加到集合中的元素比較的次數就非常多了。
也就是
說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大降低效率。
於
是,Java採用了哈希表的原理。哈希(Hash)實際上是個人名,由於他提出一哈希算法的概念,所以就以他的名字命名了。
哈希算法也稱爲散列算
法,是將數據依特定算法直接指定到一個地址上。如果詳細講解哈希算法,那需要更多的文章篇幅,我在這裏就不介紹了。
初學者可以這樣理
解,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能並不是)。
這樣一來,當集合要添加新的元素時,先調用這個元素的
hashCode方法,就一下子能定位到它應該放置的物理位置上。
如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較
了;如果這個位置上已經有元素了,
就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。
所以這裏存
在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。
所以,Java對於eqauls方法和
hashCode方法是這樣規定的:
1、如果兩個對象相同,那麼它們的hashCode值一定要相同;2、如果兩個對象的hashCode相同,
它們並不一定相同 上面說的對象相同指的是用eqauls方法比較。
你當然可以不按要求去做了,但你會發現,相同的對象可以出現在
Set集合中。同時,增加新元素的效率會大大下降。
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);
/*實
質上在添加s1,s2時,運用上面說到的兩點準則,可以知道hashset認爲s1和s2是相等的,是在添加重複元素,所以讓s2覆蓋了s1;*/
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()方法後才能進行判斷是否重複,這當然會引起效率的大大降低。