關於equals()和hashcode()的一些約定

Virtuals發表於2021-06-30

本文章主要討論和回答一下幾個問題:

  • equals()的四大特性

  • equals()和hashcode()之間的關係,為什麼我們經常說這兩個方法要麼都重寫,要麼都不重寫?

  • HashMap、HashSet等容器為什麼要求一定要重寫equals()以及hashcode()

equals()

equals和hashcode方法我們都很瞭解,是Object類中的定義的方法,這意味著所有的類都隱式實現了這兩個方法。

Object類中的equals方法的預設實現是比較物件標識(根據物件頭資訊),但是這個對我們沒有任何意義。因此一般情況下我們要重寫equals方法

equals方法一般有以下四個約定:

  • 自反:物件必須等於自身

  • 對稱:x.equals(y) 必須返回與 y.equals(x) 相同的結果

  • 傳遞性:如果 x.equals(y) 和 y.equals(z) 那麼 x.equals(z)

  • 一致:僅當包含在 equals() 中的屬性發生更改時,equals() 的值才應更改

使用IDEA智慧重寫equals方法如下,比較兩個物件相關屬性的值是否全部一致。:

public class Student {
    private String name;
    private int age;
}
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

hashcode()

hashcode方法也同樣定義在Object類中,返回一個整數,表示該類的例項狀態,要根據類的相等性定義來計算這個值,也就是說hashcode方法呼叫了equals方法,因此重寫hashcode必須先重寫equals,這樣類的hashcode值才有意義。

hashcode同樣有取得共識的約定:

  • 內部一致性:只有當 equals() 中的屬性發生變化時,hashCode() 的值才會發生變化
  • 相等一致性:彼此相等的物件必須返回相同的 hashCode
  • hash碰撞:不相等的物件可能具有相同的雜湊碼

從第二個約定我們可以推出,重寫equals方法也必須同時重寫hashcode方法,不然就違反了第二個規定

看到這裡,我想我們已經解決了前面提出的第二個問題,equals和hashcode必須都被重寫或者都不重寫

但是,這只是一個約定,並非強制要求,如果不遵循這個約定會有什麼問題呢?我們通過hashmap來舉例

hashmap的key如何實現唯一性

我們知道map為了保證map的key是唯一的,我們需要重寫key類的hashcode方法和equals方法。為什麼呢?因為key的新增過程是這樣的:

  • 先檢視key的hashcode是否已經存在

    • 如果不存在,說明當前容器沒有此key,直接新增

    • 如果存在,有可能是相同的key,也有可能是產生了hash碰撞。使用equals進行進一步比較

因此使用hashmap必須重寫這兩個方法

如果不重寫的話,可能會有重複的key被放入map中。舉個例子:

        HashMap<Student, Integer> studentIntegerHashMap = new HashMap<Student, Integer>();
        Student tom1 = new Student("tom", 11);
        Student tom2 = new Student("tom", 11);
        studentIntegerHashMap.put(tom1,1);
        studentIntegerHashMap.put(tom2,1);

正常情況下tom2是不會被新增到map集合中的,但是如果你不重寫hashcode方法,使用的就是本地的hashcode方法,這兩個物件的hashcode一定不同,因此都能被新增進集合中,這顯然是我們不想看到的。

至於HashSet,有的朋友應該知道,HashSet的底層是通過HashMap實現的,因此也同樣要實現這兩個方法才能“去重”

總結

在本篇文章中,我們討論了 equals() 和 hashCode() 的約定和使用。我們應該記住以下幾點:

  • 如果我們覆蓋 equals(),則始終覆蓋 hashCode(),反過來也一樣

  • 考慮使用 IDE 或第三方庫來生成 equals() 和 hashCode() 方法

參考

相關文章