首先從源碼的角度來看一看equals()方法和hashcode()方法含義
equals()方法和hashcode()方法都屬於Object類,在Java中,所有的類都是Object類的子類,也就是說,任何Java對象都可調用Object類的方法。
- equals()方法
public boolean equals(Object obj) {
return (this == obj);
}
很明顯,該方法就是用來判斷兩個對象是否是同一個對象。在Object類源碼中,其底層是使用了“==”來實現,也就是說通過比較兩個對象的內存地址是否相同判斷是否是同一個對象,實際上,該equals()方法通常沒有太大的實用價值。而我們往往需要用equals()來判斷 2個對象在邏輯上是否等價,而非驗證它的唯一性。這樣我們在實現自己的類時,就要重寫equals()方法。
- hashcode()方法:
public native int 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這種的都要考慮到散列的數據類型的運用。