程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java提高篇——equals()與hashCode()方法詳解,equalshashcode

Java提高篇——equals()與hashCode()方法詳解,equalshashcode

編輯:JAVA綜合教程

Java提高篇——equals()與hashCode()方法詳解,equalshashcode


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類中如下:

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;  
}  

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

它的性質有:

  • 自反性(reflexive)。對於任意不為null的引用值x,x.equals(x)一定是true

  • 對稱性(symmetric)。對於任意不為null的引用值xy,當且僅當x.equals(y)true時,y.equals(x)也是true

  • 傳遞性(transitive)。對於任意不為null的引用值xyz,如果x.equals(y)true,同時y.equals(z)true,那麼x.equals(z)一定是true

  • 一致性(consistent)。對於任意不為null的引用值xy,如果用於equals比較的對象信息沒有被修改的話,多次調用時x.equals(y)要麼一致地返回true要麼一致地返回false

  • 對於任意不為null的引用值xx.equals(null)返回false

對於Object類來說,equals()方法在對象上實現的是差別可能性最大的等價關系,即,對於任意非null的引用值xy,當且僅當xy引用的是同一個對象,該方法才會返回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()方法如下:

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方法的次數就大大降低了,幾乎只需要一兩次。  

 簡而言之,在集合查找時,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:

 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 }

運行結果:

false
true
96321
96321
aaa

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

例2:

 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 }

運行結果:

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()方法。

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;
    }
}

運行結果:

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()方法後才能進行判斷是否重復,這當然會引起效率的大大降低。

 

文章參考:

在Java中正確地使用equals()和hashCode()方法

Java中equals()與hashCode()方法詳解

深入解析Java對象的hashCode和hashCode在HashMap的底層數據結構的應用

Java hashCode() 和 equals()的若干問題解答

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved