java中hashcode和equals的區別和聯系


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

Java代碼
  1. class A {   
  2.    
  3.     @Override   
  4.     public boolean equals(Object obj) {   
  5.         System.out.println("判斷equals");   
  6.         return false;   
  7.     }   
  8.    
  9.     @Override   
  10.     public int hashCode() {   
  11.         System.out.println("判斷hashcode");   
  12.         return 1;   
  13.     }   
  14. }   
  1. class A {   
  2.    
  3.     @Override   
  4.     public boolean equals(Object obj) {   
  5.         System.out.println("判斷equals");   
  6.         return false;   
  7.     }   
  8.    
  9.     @Override   
  10.     public int hashCode() {   
  11.         System.out.println("判斷hashcode");   
  12.         return 1;   
  13.     }   
  14. }   

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

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. }   
  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 2
可以看出,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不相等,則沒有

  1. import java.util.HashMap;   
  2. import java.util.Map;   
  3.    
  4.    
  5. class A {   
  6.    
  7.     @Override   
  8.     public boolean equals(Object obj) {   
  9.         System.out.println("判斷equals");   
  10.         return true;   
  11.     }   
  12.    
  13.     @Override   
  14.     public int hashCode() {   
  15.         System.out.println("判斷hashcode");   
  16.         return 1;   
  17.     }   
  18. }   
  19.    
  20.    
  21. public class Test {   
  22.    
  23.     public static void main(String[] args) {   
  24.         Map<A,Object> map = new HashMap<A, Object>();   
  25.         map.put(new A(), new Object());   
  26.         map.put(new A(), new Object());   
  27.            
  28.         System.out.println(map.size());   
  29.     }   
  30.        
  31. }   
  1. import java.util.HashMap;   
  2. import java.util.Map;   
  3.    
  4.    
  5. class A {   
  6.    
  7.     @Override   
  8.     public boolean equals(Object obj) {   
  9.         System.out.println("判斷equals");   
  10.         return true;   
  11.     }   
  12.    
  13.     @Override   
  14.     public int hashCode() {   
  15.         System.out.println("判斷hashcode");   
  16.         return 1;   
  17.     }   
  18. }   
  19.    
  20.    
  21. public class Test {   
  22.    
  23.     public static void main(String[] args) {   
  24.         Map<A,Object> map = new HashMap<A, Object>();   
  25.         map.put(new A(), new Object());   
  26.         map.put(new A(), new Object());   
  27.            
  28.         System.out.println(map.size());   
  29.     }   
  30.        
  31. }   

必要再調用 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。

改寫程序如下:

Java代碼

運行之后打印結果是: 判斷hashcode 判斷hashcode 判斷equals 1
顯然這時候map的長度已經變成1了,因為Java運行時環境認為存入了兩個相同的對象。原因可根據上述分析方式進行分析。
以上分析的是HashMap,其實HashSet的底層本身就是通過HashMap來實現的,所以他的判斷原理和HashMap是一樣的,也是先判斷hashcode再判斷equals。
所以:寫程序的時候應盡可能的按規范來,不然在不知不覺中就埋下了bug!

 

 

 

 

1. '=='是用來比較兩個變量(基本類型和對象類型)的值是否相等的, 如果兩個變量是基本類型的,那很容易,直接比較值就可以了。如果兩個變量是對象類型的,那么它還是比較值,只是它比較的是這兩個對象在棧中的引用(即地址)。 對象是放在堆中的,棧中存放的是對象的引用(地址)。由此可見'=='是對棧中的值進行比較的。如果要比較堆中對象的內容是否相同,那么就要重寫equals方法了。 2. Object類中的equals方法就是用'=='來比較的,所以如果沒有重寫equals方法,equals和==是等價的。 通常我們會重寫equals方法,讓equals比較兩個對象的內容,而不是比較對象的引用(地址)因為往往我們覺得比較對象的內容是否相同比比較對象的引用(地址)更有意義。 3. Object類中的hashCode是返回對象在內存中地址轉換成的一個int值(可以就當做地址看)。所以如果沒有重寫hashCode方法,任何對象的hashCode都是不相等的。通常在集合類的時候需要重寫hashCode方法和equals方法,因為如果需要給集合類(比如:HashSet)添加對象,那么在添加之前需要查看給集合里是否已經有了該對象,比較好的方式就是用hashCode。 4. 注意的是String、Integer、Boolean、Double等這些類都重寫了equals和hashCode方法,這兩個方法是根據對象的內容來比較和計算hashCode的。(詳細可以查看jdk下的String.java源代碼),所以只要對象的基本類型值相同,那么hashcode就一定相同。

5. equals()相等的兩個對象,hashcode()一般是相等的,最好在重寫equals()方法時,重寫hashcode()方法; equals()不相等的兩個對象,卻並不能證明他們的hashcode()不相等。換句話說,equals()方法不相等的兩個對象,hashcode()有可能相等。 反過來:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。在object類中,hashcode()方法是本地方法,返回的是對象的引用(地址值),而object類中的equals()方法比較的也是兩個對象的引用(地址值),如果equals()相等,說明兩個對象地址值也相等,當然hashcode()也就相等了。

 

有許多人學了很長時間的Java,但一直不明白hashCode方法的作用,我來解釋一下吧。首先,想要明白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集合中。同時,增加新元素的效率會大大下降

 

 

下面是一個重寫equals和hashcode的例子:

  • public class Cat
  •     private String name; 
  •     private String birthday; 
  •  
  •     public Cat() 
  •     { 
  •     } 
  •  
  •     public void setName(String name) 
  •     { 
  •         this.name = name; 
  •     } 
  •  
  •     public String getName() 
  •     { 
  •         return name; 
  •     } 
  •  
  •     public void setBirthday(String birthday) 
  •     { 
  •         this.birthday = birthday; 
  •     } 
  •  
  •     public String getBirthday() 
  •     { 
  •         return birthday; 
  •     } 
  •  
  •        //重寫equals方法 
  •     public boolean equals(Object other) 
  •     { 
  •         if(this == other) 
  •         { 
  •             //如果引用地址相同,即引用的是同一個對象,就返回true 
  •             return true; 
  •         } 
  •  
  •             //如果other不是Cat類的實例,返回false 
  •         if(!(other instanceOf Cat)) 
  •         { 
  •             return false; 
  •         } 
  •  
  •         final Cat cat = (Cat)other; 
  •             //name值不同,返回false 
  •         if(!getName().equals(cat.getName()) 
  •             return false; 
  •             //birthday值不同,返回false 
  •         if(!getBirthday().equals(cat.getBirthday())) 
  •             return false; 
  •  
  •         return true; 
  •     } 
  •  
  •        //重寫hashCode()方法 
  •     public int hashCode() 
  •     { 
  •         int result = getName().hashCode(); 
  •         result = 29 * result + getBirthday().hashCode(); 
  •         return result; 
  •     } 


免責聲明!

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



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