Java實戰equals()與hashCode()

一.equals()方法詳解 equals()方法在object類中定義以下:java

  代碼算法

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }

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

  代碼this

  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String anotherString = (String)anObject;
  7. int n = count;
  8. if (n == anotherString.count) {
  9. char v1[] = value;
  10. char v2[] = anotherString.value;
  11. int i = offset;
  12. int j = anotherString.offset;
  13. while (n– != 0) {
  14. if (v1[i++] != v2[j++])
  15. return false;
  16. }
  17. return true;
  18. }
  19. }
  20. return false;
  21. }

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

  應注意,Java語言對equals()的要求以下,這些要求是必須遵循的:code

  1.對稱性:若是x.equals(y)返回是「true」,那麼y.equals(x)也應該返回是「true」。對象

  2.反射性:x.equals(x)必須返回是「true」。繼承

  3.類推性:若是x.equals(y)返回是「true」,並且y.equals(z)返回是「true」,那麼z.equals(x)也應該返回是「true」。接口

  4.一致性:若是x.equals(y)返回是「true」,只要x和y內容一直不變,無論你重複x.equals(y)多少次,返回都是「true」。字符串

  5.任何狀況下,x.equals(null),永遠返回是「false」;x.equals(和x不一樣類型的對象)永遠返回是「false」。

  以上這五點是重寫equals()方法時,必須遵照的準則,若是違反會出現意想不到的結果。

  二.hashcode() 方法詳解

  在object類中,hashCode定義以下:

  代碼

  1. public native int hashCode();

  說明是一個本地方法,它的實現是根據本地機器相關的。固然咱們能夠在本身寫的類中覆蓋hashcode()方法,好比String、Integer、Double等這些類都是覆蓋了hashcode()方法的。例如在String類中定義的hashcode()方法以下:

  代碼

  1. public int hashCode() {
  2. int h = hash;
  3. if (h == 0) {
  4. int off = offset;
  5. char val[] = value;
  6. int len = count;
  7. for (int i = 0; i < len; i++) {
  8. h = 31 * h + val[off++];
  9. }
  10. hash = h;
  11. }
  12. return h;
  13. }
  14. 解釋一下這個程序(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()可能相等,也可能不等。

  在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. import java.util.HashSet;
  3. import java.util.Iterator;
  4. import java.util.Set;
  5. public class HashSetTest {
  6. public static void main(String args[]) {
  7. String s1 = new String("aaa");
  8. String s2 = new String("aaa");
  9. System.out.println(s1 == s2);
  10. System.out.println(s1.equals(s2));
  11. System.out.println(s1.hashCode());
  12. System.out.println(s2.hashCode());
  13. Set hashset = new HashSet();
  14. hashset.add(s1);
  15. hashset.add(s2);
  16. Iterator it = hashset.iterator();
  17. while (it.hasNext()) {
  18. System.out.println(it.next());
  19. }
  20. }
  21. }
  22. 運行結果:
  23. Text代碼 收藏代碼
  24. false
  25. true
  26. 96321
  27. 96321
  28. aaa

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

  例2:

  代碼

  1. package com.bijian.study;
  2. import java.util.HashSet;
  3. import java.util.Iterator;
  4. public class HashSetTest {
  5. public static void main(String[] args) {
  6. HashSet hs = new HashSet();
  7. hs.add(new Student(1, "zhangsan"));
  8. hs.add(new Student(2, "lisi"));
  9. hs.add(new Student(3, "wangwu"));
  10. hs.add(new Student(1, "zhangsan"));
  11. Iterator it = hs.iterator();
  12. while (it.hasNext()) {
  13. System.out.println(it.next());
  14. }
  15. }
  16. }
  17. class Student {
  18. int num;
  19. String name;
  20. Student(int num, String name) {
  21. this.num = num;
  22. this.name = name;
  23. }
  24. public String toString() {
  25. return num + ":" + name;
  26. }
  27. }
  28. 運行結果:
  29. Text代碼 收藏代碼
  30. 1:zhangsan
  31. 3:wangwu
  32. 2:lisi
  33. 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()方法。

  代碼

  1. class Student {
  2. int num;
  3. String name;
  4. Student(int num, String name) {
  5. this.num = num;
  6. this.name = name;
  7. }
  8. public int hashCode() {
  9. return num * name.hashCode();
  10. }
  11. public boolean equals(Object o) {
  12. Student s = (Student) o;
  13. return num == s.num && name.equals(s.name);
  14. }
  15. public String toString() {
  16. return num + ":" + name;
  17. }
  18. }
  19. 運行結果:
  20. Text代碼 收藏代碼
  21. 1:zhangsan
  22. 3:wangwu
  23. 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()這個方法,只是爲了提升效率,且這是很是有必要的。好比能夠這樣寫:

  代碼

  1. public int hashCode(){
  2. return 1; //等價於hashcode無效
  3. }

  這樣作的效果就是在比較哈希碼的時候不能進行判斷,由於每一個對象返回的哈希碼都是1,每次都必需要通過比較equals()方法後才能進行判斷是否重複,這固然會引發效率的大大下降。

                                                                                                       技術分享:www.kaige123.com

相關文章
相關標籤/搜索