天天看點

java 中equals()和hashCode()方法詳解

Java基礎中有些知識點很容易忘記,看了n遍忘了n遍。比如Java中的equals 方法和hashCode方法。我想大家對equals 方法還相對清楚些,object 中的equals方法是引用位址的比較,字元串的比較是内容的比較,因為String類中重寫了equals方法。而對hashCode方法了解甚少,隻知道hashCode相等的兩個對象equals方法不一定相等,而equals方法相等的兩個對象hashCode一定相等,重寫equals方法也要重寫hashCode方法。是以特地轉載一篇相關的文章來鞏固一下以及在以後的日子裡友善檢視。希望大家共同學習。

原文連結:http://www.cnblogs.com/Qian123/p/5703507.html點選打開連結

java.lang.Object類中有兩個非常重要的方法:

1 2

public

boolean

equals(Object obj)

public

int

hashCode()

Object

類是類繼承結構的基礎,是以是每一個類的父類。所有的對象,包括數組,都實作了在

Object

類中定義的方法。

回到頂部

equals()方法詳解

equals()

方法是用來判斷其他的對象是否和該對象相等.

  equals()方法在object類中定義如下: 

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

很明顯是對兩個對象的位址值進行的比較(即比較引用是否相同)。但是我們知道,String 、Math、Integer、Double等這些封裝類在使用equals()方法時,已經覆寫了object類的equals()方法。

  比如在String類中如下:

java 中equals()和hashCode()方法詳解
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;  
}        
java 中equals()和hashCode()方法詳解

很明顯,這是進行的内容比較,而已經不再是位址的比較。依次類推Math、Integer、Double等這些類都是重寫了equals()方法的,進而進行的是内容的比較。當然,基本類型是進行值的比較。

它的性質有:

  • 自反性(reflexive)。對于任意不為

    null

    的引用值x,

    x.equals(x)

    一定是

    true

  • 對稱性(symmetric)。對于任意不為

    null

    的引用值

    x

    y

    ,當且僅當

    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比較的對象資訊沒有被修改的話,多次調用時

    x.equals(y)

    要麼一緻地傳回

    true

    要麼一緻地傳回

    false

  • 對于任意不為

    null

    的引用值

    x

    x.equals(null)

    傳回

    false

對于

Object

類來說,

equals()

方法在對象上實作的是差别可能性最大的等價關系,即,對于任意非

null

的引用值

x

y

,當且僅當

x

y

引用的是同一個對象,該方法才會傳回

true

需要注意的是當

equals()

方法被override時,

hashCode()

也要被override。按照一般

hashCode()

方法的實作來說,相等的對象,它們的hash code一定相等。

回到頂部

hashcode() 方法詳解

hashCode()

方法給對象傳回一個hash code值。這個方法被用于hash tables,例如HashMap。

它的性質是:

  • 在一個Java應用的執行期間,如果一個對象提供給equals做比較的資訊沒有被修改的話,該對象多次調用

    hashCode()

    方法,該方法必須始終如一傳回同一個integer。
  • 如果兩個對象根據

    equals(Object)

    方法是相等的,那麼調用二者各自的

    hashCode()

    方法必須産生同一個integer結果。
  • 并不要求根據

    equals(java.lang.Object)

    方法不相等的兩個對象,調用二者各自的

    hashCode()

    方法必須産生不同的integer結果。然而,程式員應該意識到對于不同的對象産生不同的integer結果,有可能會提高hash table的性能。

大量的實踐表明,由

Object

類定義的

hashCode()

方法對于不同的對象傳回不同的integer。

在object類中,hashCode定義如下:

public native int hashCode();      

 說明是一個本地方法,它的實作是根據本地機器相關的。當然我們可以在自己寫的類中覆寫hashcode()方法,比如String、Integer、Double等這些類都是覆寫了hashcode()方法的。例如在String類中定義的hashcode()方法如下:

java 中equals()和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;  
}        
java 中equals()和hashCode()方法詳解

解釋一下這個程式(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方法的次數就大大降低了,幾乎隻需要一兩次。  

 簡而言之,在集合查找時,hashcode能大大降低對象比較次數,提高查找效率!

Java對象的eqauls方法和hashCode方法是這樣規定的:

1、相等(相同)的對象必須具有相等的哈希碼(或者散列碼)。

2、如果兩個對象的hashCode相同,它們并不一定相同。

 以下是Object對象API關于equal方法和hashCode方法的說明:

  • If two objects are equal according to the 

    equals(Object)

     method, then calling the 

    hashCode

     method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the 

    equals(java.lang.Object)

     method, then calling the 

    hashCode

     method 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點的官方詳細說明

關于第一點,相等(相同)的對象必須具有相等的哈希碼(或者散列碼),為什麼?

 想象一下,假如兩個Java對象A和B,A和B相等(eqauls結果為true),但A和B的哈希碼不同,則A和B存入HashMap時的哈希碼計算得到的HashMap内部數組位置索引可能不同,那麼A和B很有可能允許同時存入HashMap,顯然相等/相同的元素是不允許同時存入HashMap,HashMap不允許存放重複元素。

 關于第二點,兩個對象的hashCode相同,它們并不一定相同

 也就是說,不同對象的hashCode可能相同;假如兩個Java對象A和B,A和B不相等(eqauls結果為false),但A和B的哈希碼相等,将A和B都存入HashMap時會發生哈希沖突,也就是A和B存放在HashMap内部數組的位置索引相同這時HashMap會在該位置建立一個連結表,将A和B串起來放在該位置,顯然,該情況不違反HashMap的使用原則,是允許的。當然,哈希沖突越少越好,盡量采用好的雜湊演算法以避免哈希沖突。

 是以,Java對于eqauls方法和hashCode方法是這樣規定的:     

   1.如果兩個對象相同,那麼它們的hashCode值一定要相同;

      2.如果兩個對象的hashCode相同,它們并不一定相同(這裡說的對象相同指的是用eqauls方法比較)。  

        如不按要求去做了,會發現相同的對象可以出現在Set集合中,同時,增加新元素的效率會大大下降。

      3.equals()相等的兩個對象,hashcode()一定相等;equals()不相等的兩個對象,卻并不能證明他們的hashcode()不相等。

        換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等(我的了解是由于哈希碼在生成的時候産生沖突造成的)。反過來,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

        在object類中,hashcode()方法是本地方法,傳回的是對象的位址值,而object類中的equals()方法比較的也是兩個對象的位址值,如果equals()相等,說明兩個對象位址值也相等,當然hashcode()也就相等了;在String類中,equals()傳回的是兩個對象内容的比較,當兩個對象内容相等時,Hashcode()方法根據String類的重寫代碼的分析,也可知道hashcode()傳回結果也會相等。以此類推,可以知道Integer、Double等封裝類中經過重寫的equals()和hashcode()方法也同樣适合于這個原則。當然沒有經過重寫的類,在繼承了object類的equals()和hashcode()方法後,也會遵守這個原則。

回到頂部

Hashset、Hashmap、Hashtable與hashcode()和equals()的密切關系

Hashset是繼承Set接口,Set接口又實作Collection接口,這是層次關系。那麼Hashset、Hashmap、Hashtable中的存儲操作是根據什麼原理來存取對象的呢?

        下面以HashSet為例進行分析,我們都知道:在hashset中不允許出現重複對象,元素的位置也是不确定的。在hashset中又是怎樣判定元素是否重複的呢?在java的集合中,判斷兩個對象是否相等的規則是:

         1.判斷兩個對象的hashCode是否相等

             如果不相等,認為兩個對象也不相等,完畢

             如果相等,轉入2

           (這一點隻是為了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,是以我們這裡将其做為必需的。)

         2.判斷兩個對象用equals運算是否相等

            如果不相等,認為兩個對象也不相等

            如果相等,認為兩個對象相等(equals()是判斷兩個對象是否相等的關鍵)

            為什麼是兩條準則,難道用第一條不行嗎?不行,因為前面已經說了,hashcode()相等時,equals()方法也可能不等,是以必須用第2條準則進行限制,才能保證加入的為非重複元素。

例1:

java 中equals()和hashCode()方法詳解
1 package com.bijian.study;
 2 
 3 import java.util.HashSet;
 4 import java.util.Iterator;
 5 import java.util.Set;
 6 
 7 public class HashSetTest {
 8 
 9     public static void main(String args[]) {
10         String s1 = new String("aaa");
11         String s2 = new String("aaa");
12         System.out.println(s1 == s2);
13         System.out.println(s1.equals(s2));
14         System.out.println(s1.hashCode());
15         System.out.println(s2.hashCode());
16         Set hashset = new HashSet();
17         hashset.add(s1);
18         hashset.add(s2);
19         Iterator it = hashset.iterator();
20         while (it.hasNext()) {
21             System.out.println(it.next());
22         }
23     }
24 }      
java 中equals()和hashCode()方法詳解

運作結果:

false
true
96321
96321
aaa      

  這是因為String類已經重寫了equals()方法和hashcode()方法,是以hashset認為它們是相等的對象,進行了重複添加。

例2:

java 中equals()和hashCode()方法詳解
1 package com.bijian.study;
 2 
 3 import java.util.HashSet;
 4 import java.util.Iterator;
 5 
 6 public class HashSetTest {
 7 
 8     public static void main(String[] args) {
 9         HashSet hs = new HashSet();
10         hs.add(new Student(1, "zhangsan"));
11         hs.add(new Student(2, "lisi"));
12         hs.add(new Student(3, "wangwu"));
13         hs.add(new Student(1, "zhangsan"));
14 
15         Iterator it = hs.iterator();
16         while (it.hasNext()) {
17             System.out.println(it.next());
18         }
19     }
20 }
21 
22 class Student {
23     int num;
24     String name;
25 
26     Student(int num, String name) {
27         this.num = num;
28         this.name = name;
29     }
30 
31     public String toString() {
32         return num + ":" + name;
33     }
34 }      
java 中equals()和hashCode()方法詳解

運作結果:

1:zhangsan  
3:wangwu  
2:lisi  
1:zhangsan       

為什麼hashset添加了相等的元素呢,這是不是和hashset的原則違背了呢?回答是:沒有。因為在根據hashcode()對兩次建立的new Student(1,“zhangsan”)對象進行比較時,生成的是不同的哈希碼值,是以hashset把他當作不同的對象對待了,當然此時的equals()方法傳回的值也不等。

        為什麼會生成不同的哈希碼值呢?上面我們在比較s1和s2的時候不是生成了同樣的哈希碼嗎?原因就在于我們自己寫的Student類并沒有重新自己的hashcode()和equals()方法,是以在比較時,是繼承的object類中的hashcode()方法,而object類中的hashcode()方法是一個本地方法,比較的是對象的位址(引用位址),使用new方法建立對象,兩次生成的當然是不同的對象了,造成的結果就是兩個對象的hashcode()傳回的值不一樣,是以Hashset會把它們當作不同的對象對待。

        怎麼解決這個問題呢?答案是:在Student類中重新hashcode()和equals()方法。

java 中equals()和hashCode()方法詳解
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;
    }
}      
java 中equals()和hashCode()方法詳解

運作結果:

1:zhangsan  
3:wangwu  
2:lisi        

可以看到重複元素的問題已經消除,根據重寫的方法,即便兩次調用了new Student(1,"zhangsan"),我們在獲得對象的哈希碼時,根據重寫的方法hashcode(),獲得的哈希碼肯定是一樣的,當然根據equals()方法我們也可判斷是相同的,是以在向hashset集合中添加時把它們當作重複元素看待了。

重寫equals()和hashcode()小結:

  1.重點是equals,重寫hashCode隻是技術要求(為了提高效率)

      2.為什麼要重寫equals呢?因為在java的集合架構中,是通過equals來判斷兩個對象是否相等的

      3.在hibernate中,經常使用set集合來儲存相關對象,而set集合是不允許重複的。在向HashSet集合中添加元素時,其實隻要重寫equals()這一條也可以。但當hashset中元素比較多時,或者是重寫的equals()方法比較複雜時,我們隻用equals()方法進行比較判斷,效率也會非常低,是以引入了hashCode()這個方法,隻是為了提高效率,且這是非常有必要的。比如可以這樣寫:

public int hashCode(){  
   return 1; //等價于hashcode無效  
}        

這樣做的效果就是在比較哈希碼的時候不能進行判斷,因為每個對象傳回的哈希碼都是1,每次都必須要經過比較equals()方法後才能進行判斷是否重複,這當然會引起效率的大大降低。