如果兩個對象不相同,他們的hashcode可能相同?


HashSet和HashMap一直都是JDK中最常用的兩個類,HashSet要求不能存儲相同的對象,HashMap要求不能存儲相同的鍵。 
那么Java運行時環境是如何判斷HashSet中相同對象、HashMap中相同鍵的呢?當存儲了“相同的東西”之后Java運行時環境又將如何來維護呢? 

在研究這個問題之前,首先說明一下JDK對equals(Object obj)和hashcode()這兩個方法的定義和規范: 
在Java中任何一個對象都具備equals(Object obj)和hashcode()這兩個方法,因為他們是在Object類中定義的。 
equals(Object obj)方法用來判斷兩個對象是否“相同”,如果“相同”則返回true,否則返回false。 
hashcode()方法返回一個int數,在Object類中的默認實現是“將該對象的內部地址轉換成一個整數返回”。 
接下來有兩個個關於這兩個方法的重要規范(我只是抽取了最重要的兩個,其實不止兩個): 
規范1:若重寫equals(Object obj)方法,有必要重寫hashcode()方法,確保通過equals(Object obj)方法判斷結果為true的兩個對象具備相等的hashcode()返回值。說得簡單點就是:“如果兩個對象相同,那么他們的hashcode應該 相等”。不過請注意:這個只是規范,如果你非要寫一個類讓equals(Object obj)返回true而hashcode()返回兩個不相等的值,編譯和運行都是不會報錯的。不過這樣違反了Java規范,程序也就埋下了BUG。 
規范2:如果equals(Object obj)返回false,即兩個對象“不相同”,並不要求對這兩個對象調用hashcode()方法得到兩個不相同的數。說的簡單點就是:“如果兩個對象不相同,他們的hashcode可能相同”。 
根據這兩個規范,可以得到如下推論: 
1、如果兩個對象equals,Java運行時環境會認為他們的hashcode一定相等。 
2、如果兩個對象不equals,他們的hashcode有可能相等。 
3、如果兩個對象hashcode相等,他們不一定equals。 
4、如果兩個對象hashcode不相等,他們一定不equals。 

這樣我們就可以推斷Java運行時環境是怎樣判斷HashSet和HastMap中的兩個對象相同或不同了。我的推斷是:先判斷hashcode是否相等,再判斷是否equals。 


測試程序如下:首先我們定義一個類,重寫hashCode()和equals(Object obj)方法 

class A { 
 
    @Override 
    public boolean equals(Object obj) { 
        System.out.println("判斷equals"); 
        return false; 
    } 
 
    @Override 
    public int hashCode() { 
        System.out.println("判斷hashcode"); 
        return 1; 
    } 

 

然后寫一個測試類,代碼如下: 

Java代碼
  1. public class Test {    
  2.     
  3.     public static void main(String[] args) {    
  4.         Map<A,Object> map = new HashMap<A, Object>();    
  5.         map.put(new A(), new Object());    
  6.         map.put(new A(), new Object());    
  7.             
  8.         System.out.println(map.size());    
  9.     }    
  10.         
  11. }    

 

運行之后打印結果是: 

判斷hashcode 
判斷hashcode 
判斷equals 


可以看出,Java運行時環境會調用new A()這個對象的hashcode()方法。其中: 
打印出的第一行“判斷hashcode”是第一次map.put(new A(), new Object())所打印出的。 
接下來的“判斷hashcode”和“判斷equals”是第二次map.put(new A(), new Object())所打印出來的。 

那么為什么會是這樣一個打印結果呢?我是這樣分析的: 
1、當第一次map.put(new A(), new Object())的時候,Java運行時環境就會判斷這個map里面有沒有和現在添加的 new A()對象相同的鍵,判斷方法:調用new A()對象的hashcode()方法,判斷map中當前是不是存在和new A()對象相同的HashCode。顯然,這時候沒有相同的,因為這個map中都還沒有東西。所以這時候hashcode不相等,則沒有必要再調用 equals(Object obj)方法了。參見推論4(如果兩個對象hashcode不相等,他們一定不equals) 
2、當第二次map.put(new A(), new Object())的時候,Java運行時環境再次判斷,這時候發現了map中有兩個相同的hashcode(因為我重寫了A類的hashcode()方 法永遠都返回1),所以有必要調用equals(Object obj)方法進行判斷了。參見推論3(如果兩個對象hashcode相等,他們不一定equals),然后發現兩個對象不equals(因為我重寫了equals(Object obj)方法,永遠都返回false)。 
3、這時候判斷結束,判斷結果:兩次存入的對象不是相同的對象。所以最后打印map的長度的時候顯示結果是:2。 

改寫程序如下: 

import java.util.HashMap; 
import java.util.Map; 
 
 
class A { 
 
    @Override 
    public boolean equals(Object obj) { 
        System.out.println("判斷equals"); 
        return true; 
    } 
 
    @Override 
    public int hashCode() { 
        System.out.println("判斷hashcode"); 
        return 1; 
    } 

 
 
public class Test { 
 
    public static void main(String[] args) { 
        Map<A,Object> map = new HashMap<A, Object>(); 
        map.put(new A(), new Object()); 
        map.put(new A(), new Object()); 
         
        System.out.println(map.size()); 
    } 
     

 

運行之后打印結果是: 

判斷hashcode 
判斷hashcode 
判斷equals 



顯然這時候map的長度已經變成1了,因為Java運行時環境認為存入了兩個相同的對象。原因可根據上述分析方式進行分析。 

以上分析的是HashMap,其實HashSet的底層本身就是通過HashMap來實現的,所以他的判斷原理和HashMap是一樣的,也是先判斷hashcode再判斷equals。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM