首先從源碼的角度來看一看equals()方法和hashcode()方法含義
equals()方法和hashcode()方法都屬于Object類,在Java中,所有的類都是Object類的子類,也就是說,任何Java對象都可調用Object類的方法。
- equals()方法
public boolean equals(Object obj) {
return (this == obj);
}
很明顯,該方法就是用來判斷兩個對象是否是同一個對象。在Object類源碼中,其底層是使用了“==”來實作,也就是說通過比較兩個對象的記憶體位址是否相同判斷是否是同一個對象,實際上,該equals()方法通常沒有太大的實用價值。而我們往往需要用equals()來判斷 2個對象在邏輯上是否等價,而非驗證它的唯一性。這樣我們在實作自己的類時,就要重寫equals()方法。
- hashcode()方法:
一提到hashcode,很自然就想到哈希表。将某一key值映射到表中的一個位置,進而達到以O(1)的時間複雜度來查詢該key值。Object類源碼中,hashCode()是一個native方法,哈希值的計算利用的是記憶體位址。
我們看一下Object類中關于hashCode()方法的注釋
1,在Java運用程式執行期間,在對同一對象多次調用hashCode方法時,必須一緻地傳回相同的整數,前提是将對象進行equals比較時所用的資訊沒有被修改。從某一運用程式的一次執行到同一運用程式的另外一次執行,該整數無需保持一緻。
2.如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用 hashCode 方法都必須生成相同的整數結果。
3.如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不要求一定生成不同的整數結果。但是,程式員應該意識到,為不相等的對象生成不同整數結果可以提高哈希表的性能。
通俗的講,注釋中第二點和第三點的含義就是equals()和hashcode()方法要保持相當程度的一緻性,equals()方法相等,hashcode()必須相等;反之,equals方法不相等,hashcode可以相等,可以不相等。但是兩者的一緻有利于提高哈希表的性能。
是以,源碼注釋來看,兩方法的同時重寫是很必要的。
實際來看,不同時重寫将如何?
equals()相等的的兩個等價對象因為hashCode不同,是以在hashmap中的table數組的下标不同,進而這兩個對象就會同時存在于集合中,在調用hashmap集合中的方法時就會出現邏輯的錯誤,也就是,你的equals()方法也“白白”重寫了。
是以,對于“為什麼重寫equals()就一定要重寫hashCode()方法?”這個問題應該是有個前提,就是你需要用到HashMap,HashSet等Java集合。用不到哈希表的話,其實僅僅重寫equals()方法也可以吧。而工作中的場景是常常用到Java集合,是以Java官方建議重寫equals()就一定要重寫hashCode()方法。
HashMap存儲資料的時候,是取的key值的哈希值,然後計算數組下标,采用鍊位址法解決沖突,然後進行存儲。取資料的時候,依然是先要擷取到哈希值,找到數組下标,然後for周遊連結清單集合,進行比較是否有對應的key。比較關心的有兩點:
無論是 put 還是 get 的時候,都需要得到key的哈希值,去定位key的數組下标;
在 get 的時候,需要調用equals方法比較是否有相等的key存儲過。
package com.evan.springboot.study;
import java.util.HashMap;
/**
* @author evanYang
* @version 1.0
* @date 2020/5/13 下午 2:44
*/
public class Demo {
private int a;
public Demo(int a){
this.a=a;
}
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo,1);
Integer value = map.get(new Demo(1));
if(value!=null){
System.out.println(value);
}else{
System.out.println("value is null");
}
}
}
-
分析:
Map的key使我們自定義的類,這裡沒有重寫equals()和hashcode()方法,我們列印一下hashcode看下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo,1);
System.out.println("put 時的hashCode"+demo.hashCode());
Demo demo1 = new Demo(1);
System.out.println("get 時的hashCode"+demo1.hashCode());
Integer value = map.get(demo1);
if(value!=null){
System.out.println(value);
}else{
System.out.println("value is null");
}
}
hashCode不一緻,由此而拿不到資料。這兩個key,在Map計算的時候,數組下标可能就不一緻,就算資料下标碰巧一緻,根據前面,最後equals比較的時候也不可能相等(很顯然這是兩個對象,在堆上的位址必定不一樣)。
- 現在我們不重寫hashCode(),equals()方法
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 時的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 時的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
這裡認為是同一個key
- 假如重寫了equals方法,将這兩個對象都put進去,根據map的原理,隻要是key一樣,後面的值會替換前面的值,實驗如下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 時的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 時的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Demo) {
return true;
}else {
return false;
}
}
同樣的一個對象,為什麼在map中存了兩份,map的key值不是不能重複嗎?
沒錯,它就是存的兩份。隻不過在它看來,這兩個的key是不一樣的,因為它們的哈希碼就是不一樣的,可以列印看下輸出的hash碼确實不一樣。那怎麼辦?隻有重寫hashCode()方法,更改後的代碼如下
public static void main(String[] args) {
HashMap<Demo, Integer> map = new HashMap<>();
Demo demo = new Demo(1);
map.put(demo, 1);
System.out.println("put 時的hashCode" + demo.hashCode());
Demo demo1 = new Demo(1);
map.put(demo1, 2);
System.out.println("get 時的hashCode" + demo1.hashCode());
Integer value = map.get(demo);
System.out.println(value);
Integer value1 = map.get(demo1);
System.out.println(value1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Demo;
}
@Override
public int hashCode() {
int result = 1;
result = 31 * result + (a != null ? a.hashCode() : 0);
return result;
}
可以看到,它們的hash碼是一緻的,且最後的結果也是預期的。
總結:
Map中存了兩個數值一樣的key,這個問題很嚴重。是以在重寫equals方法的時候,一定要重寫hashCode方法。
類似HashMap、HashTable、HashSet這種的都要考慮到散列的資料類型的運用。