天天看點

經典面試題:重寫equals方法時,為什麼必須重寫hashCode方法?

大家好,這裡是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)的整個過程:

  1. 擷取key的hashcode:

通過key.hashCode()方法擷取int類型的hacode值

  1. 确定put進來的元素應該放在數組的什麼位置:

通過 key.hashCode() & (數組大小 - 1) 來确定數組的位置,也就是key的hashcode對數組長度取模,數組預設長度是16,假設:key的hashcode是1,那麼通過:1&(16-1) = 1&15 = 1 得到數組的下标為1,也就是說這個元素會放在數組的第2個位置。

  1. 用連結清單幹什麼?

不同的key,hashcode可能相同,也就是說不同的key,他們可能會放在數組的相同位置,那麼如何存放呢?這個地方使用連結清單将多個元素連接配接起來,存放多個元素。

經典面試題:重寫equals方法時,為什麼必須重寫hashCode方法?

put的過程大家知道了,我們再來看看get的過程

  1. 擷取key在數組的什麼位置

    通過key的hashcode找到元素在數組的什麼位置,算法:key.hashCode() & (數組大小 - 1)

  2. 周遊連結清單查找元素

    取出數組中步驟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           

到此大家都懂了吧,有收獲的幫忙分享一下,後面會分享更多經典面試題,歡迎關注,謝謝!

經典面試題:重寫equals方法時,為什麼必須重寫hashCode方法?

·END·

經典面試題:重寫equals方法時,為什麼必須重寫hashCode方法?

掃描二維碼 | 關注我們