java中hashcode()和equals()的詳解

 今天下午研究了半天hashcode()和equals()方法,終於有了一點點的明白,寫下來與你們分享
java

1. 首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。 
equals()方法在object類中定義以下: 
算法

  public boolean equals(Object obj) { 
return (this == obj); 
}


很明顯是對兩個對象的地址值進行的比較(即比較引用是否相同)。可是咱們必需清楚,當String 、Math、還有Integer、Double。。。。等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法。好比在String類中以下: 
 
框架



很明顯,這是進行的內容比較,而已經再也不是地址的比較。依次類推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()方法以下: 
   this

 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條準則進行限制,才能保證加入的爲非重複元素。 
好比下面的代碼: 

spa

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;*/ 
hibernate

Iterator it=hashset.iterator(); 
            while(it.hasNext()) 
            { 
             System.out.println(it.next()); 
            }


最後在while循環的時候只打印出了一個」zhaoxudong」。 
輸出結果爲:false 
            true 
            -967303459 
            -967303459 
這是由於String類已經重寫了equals()方法和hashcode()方法,因此在根據上面的第1.2條原則斷定時,hashset認爲它們是相等的對象,進行了重複添加。 
可是看下面的程序: 
code

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中的存儲操做,依然遵照上面的準則。因此這裏再也不多說。這些是今天看書,網上查詢資料,本身總結出來的,部分代碼和語言是引述,可是千真萬確是本身總結出來的。有錯誤之處和不詳細不清楚的地方還請你們指出,我也是初學者,因此不免會有錯誤的地方,但願你們共同討論。
繼承

相關文章
相關標籤/搜索