大家好,這裡是java研究所。
今天給大家來個經典面試題:重寫equals方法時,為什麼必須重新hashCode方法?
這2個方法位于Object類中,Object類是所有類的父類,是以如果你的類中沒有重寫這2個方法時,預設将使用Object中的這2個方法。
equals方法
public boolean equals(Object obj) {
return (this == obj);
}
用來判斷2個對象是否相等,使用=來判斷的,也就是判斷的是引用,當2個對象的記憶體位址一樣的時候才相等。
hashCode方法
public native int hashCode();
一個native方法,用來傳回對象的hash碼,int類型的。
要搞懂開頭的問題,我們需要先了解一下這個2個方法到底有何用?
這個要從HashMap來說起,了解了HashMap的原理之後,問題自然就搞懂了。
HashMap原理
HashMap中用來存儲key->value這樣的資料,可以通過key在快速在HashMap中擷取對應的value。
HashMap中有2個操作我們比較熟悉:put操作和get操作
- put(key,value)操作用來向hashmap中存放元素
- 而get(key)可以通過key擷取到對應的value
HashMap内部采用數組加連結清單的方式來存放資料的,數組的元素是一個連結清單結構的資料,連結清單中每個元素中會存儲key和value的資訊。
來看一下put(key,value)的整個過程:
- 擷取key的hashcode:
通過key.hashCode()方法擷取int類型的hacode值
- 确定put進來的元素應該放在數組的什麼位置:
通過 key.hashCode() & (數組大小 - 1) 來确定數組的位置,也就是key的hashcode對數組長度取模,數組預設長度是16,假設:key的hashcode是1,那麼通過:1&(16-1) = 1&15 = 1 得到數組的下标為1,也就是說這個元素會放在數組的第2個位置。
- 用連結清單幹什麼?
不同的key,hashcode可能相同,也就是說不同的key,他們可能會放在數組的相同位置,那麼如何存放呢?這個地方使用連結清單将多個元素連接配接起來,存放多個元素。
put的過程大家知道了,我們再來看看get的過程
-
擷取key在數組的什麼位置
通過key的hashcode找到元素在數組的什麼位置,算法:key.hashCode() & (數組大小 - 1)
-
周遊連結清單查找元素
取出數組中步驟1位置的元素,這個元素是一個連結清單結構,沿着連結清單一個個周遊,周遊的過程會判斷連結清單的目前節點的key是否和get方法傳入的key相等,判斷key是否相等是通過equals方法判斷的,若equals方法傳回true了,表示元素找到了
重新equals方法,未重寫hashCode方法會出現什麼結果?
來個Person類,重寫equals方法,當name和age都相等的時候equals方法才傳回true,但是沒有重寫hashcode方法
import java.util.HashMap;
public class Person {
String name; //姓名
Integer age; //年齡
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
Person person = (Person) o;
return name.equals(person.name) && age.equals(person.age);
}
public static void main(String[] args) {
HashMap<Person, String> personMap = new HashMap<>();
//建立person1
Person person1 = new Person("周潤發", 50);
personMap.put(person1, "資産10個億!");
System.out.println("第1次,通過get(person1)方法,擷取:年齡50歲的周潤發:" + personMap.get(person1));
//建立person2,name和age都和person1中的一樣
Person person2 = new Person("周潤發", 50);
System.out.println("第2次,通過get(person2)方法,擷取:年齡50歲的周潤發:" + personMap.get(person2));
System.out.println("person1.equals(person2):" + person1.equals(person2));
System.out.println("person1.hashCode() = " + person1.hashCode());
System.out.println("person2.hashCode() = " + person2.hashCode());
}
}
注意main方法中,建立了2個Person:person1和person2,他們的name和age都是一樣的,也就說person1.equals(person2)是相等的,将person1放在了personMap,然後通過personMap.get(person2)來找age為50的name為周潤發的,看看能不能找到,上面代碼輸出:
第1次,通過get(person1)方法,擷取:年齡50歲的周潤發:資産10個億!
第2次,通過get(person2)方法,擷取:年齡50歲的周潤發:null
person1.equals(person2):true
person1.hashCode() = 1725154839
person2.hashCode() = 1670675563
第1次通過person1到map中找到50歲的周潤發,而第2次通過person2到map中找不到50歲的周潤發,實際map中是存在age=50,name=周潤發的元素的,為啥找不到啊?
原因:從最後2行輸出中可以看出,person1和person2的hashCode不一樣,get方法的第一步會通過hashCode來定位數組的位置,hashcode不同,數組的位置就可以不同了,最終直接導緻找不到key對應的元素。
是以如果想找到equals相同的元素,那麼這2個元素的hashcode必須一樣,這樣定位到的數組位置是一樣的,才可以找到。
調整一下上面代碼,當equals方法相同的時候,hashcode也必須要一樣,那麼hashcode方法中通也通過name和age組合的方式來得到hashcode的值,如下:
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age.hashCode();
return result;
}
運作輸出,這次正常了
第1次,通過get(person1)方法,擷取:年齡50歲的周潤發:資産10個億!
第2次,通過get(person2)方法,擷取:年齡50歲的周潤發:資産10個億!
person1.equals(person2):true
person1.hashCode() = 671364415
person2.hashCode() = 671364415
到此大家都懂了吧,有收獲的幫忙分享一下,後面會分享更多經典面試題,歡迎關注,謝謝!
·END·
掃描二維碼 | 關注我們