本文章主要討論和回答一下幾個問題:
-
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() 方法