如何編寫出高質量的 equals 和 hashcode 方法?
什麼是 equals 和 hashcode 方法?
這要從 Object 類開始說起,我們知道 Object 類是 Java 的超類,每個類都直接或者間接的繼承了 Object 類,在 Object 中提供了 8 個基本的方法,equals 方法和 hashcode 方法就是其中的兩個。
equals 方法:Object 類中的 equals 方法用於檢測一個物件是否等於另一個物件,在 Object 類中,這個方法將判斷兩個物件是否具有相同的引用,如果兩個物件具有相同的引用,它們一定是相等的。
hashcode 方法:用來獲取雜湊碼,雜湊碼是由物件匯出的一個整數值,雜湊碼是沒有規律的,如果 x 和 y 是兩個不同的物件,那麼 x.hashCode() 與 y.hashCode() 基本上不會相同
為什麼要重寫 equals 和 hashcode 方法?
為什麼需要重寫 equals 方法和 hashcode 方法,我想主要是基於以下兩點來考慮:
1、我們已經知道了 Object 中的 equals 方法是用來判斷兩個物件的引用是否相同,但是有時候我們並不需要判斷兩個物件的引用是否相等,我們只需要兩個物件的某個特定狀態是否相等。比如對於兩篇文章來說,我只要判斷兩篇文章的連結是否相同,如果連結相同,那麼它們就是同一篇文章,我並不需要去比較其它屬性或者引用地址是否相同。
2、在某些業務場景下,我們需要使用自定義類作為雜湊表的鍵,這時候我們就需要重寫,因為如果不做特定修改的話,每個物件產生的 hashcode 基本上不可能相同,而 hashcode 決定了該元素在雜湊表中的位置,equals 決定了判斷邏輯,所以特殊情況下就需要重寫這兩個方法,才能符合我們的要求。
我們使用一個小 Demo 來模擬一下特殊場景,讓我們更好的理解為什麼需要重寫 equals 和 hashcode 方法,我們的場景是:我們有很多篇文章,我需要判斷文章是否已經存在 Set 中,兩篇文章相同的條件是訪問路徑相同。
好了,我們一起動手寫 Demo 吧,我們建立一個文章類來存放文章資訊,文章類具體設計如下:
class Article{ // 文章路徑 String url; // 文章標題 String title; public Article(String url ,String title){ this.url = url; this.title = title; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
文章類中有路徑、標題兩個屬性,在這個類中我們並沒有重寫 equals 和 hashcode 方法,所以這裡會使用超類 Object 中的 equals 和 hashcode 方法,為了防止你沒有看過 Object 類中的 equals 和 hashcode 方法,我們先一起來看一下 Object 的類中的 equals 和 hashcode 方法:
看完之後,接下來,我們編寫一個測試類,測試類程式碼如下:
public class EqualsAndHashcode {
public static void main(String[] args) {
Article article = new Article("www.baidu.com","百度一下");
Article article1 = new Article("www.baidu.com","坑B百度");
Set<Article> set = new HashSet<>();
set.add(article);
System.out.println(set.contains(article1));
}
}
在測試類中,我們例項化了兩個文章物件,文章物件的 url 都是一樣的,標題不一樣,我們將 article 物件存入到 Set 中,判斷 article1 物件是否存在 Set 中,按照我們的假設,兩篇文章的 Url 相同,則兩篇文章就應該是同一篇文章,所以這裡應該給我們返回 True,我們執行 Main 方法。得到結果如下:
我們看到了結果不是你想要的 True 而是 False ,這個原因很簡單,因為兩篇文章的訪問路徑相同就是同一篇文章,這是我們定義的規則,我們並沒有告訴我們的程式這個規則,我們沒有重寫 equals 和 hashcode 方法,所以系統在判斷的時候使用的是 Object 類預設的 equals 和 hashcode 方法,預設的 equals 方法判斷的是兩個物件的引用地址是否相同,這裡肯定是不一樣的,得到的答案就是 False 。我們需要把相等的規則告訴我們的程式,那我們就把 equals 方法重寫了。
1、重寫 equals 方法
在這裡我們先使用 IDEA 工具生成的 equals 方法,把最後的邏輯返回邏輯修改一下就好了,具體的編寫規則我們下面會介紹。最後我們的 equals 方法如下
/**
* 重寫equals方法,只要兩篇文章的url相同就是同一篇文章
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Article article = (Article) o;
return Objects.equals(url, article.url);
}
再一次執行 Main 方法,你會發現還是 False ,這是為什麼呢?我已經把判斷兩個物件相等的邏輯告訴程式了,不急,我們先來聊一聊雜湊表吧,我們知道雜湊表採用的是陣列+連結串列的結構,每個陣列上掛載著連結串列,連結串列的節點用來儲存物件資訊,而物件落到陣列的位置由 hashcode()。所以當我們呼叫 HashSet 的 add(Object o) 方法時,首先會根據o.hashCode()的返回值定位到相應的陣列位置,如果該陣列位置上沒有結點,則將 o 放到這裡,如果已經有結點了, 則把 o 掛到連結串列末端。同理,當呼叫 contains(Object o) 時,Java 會通過 hashCode()的返回值定位到相應的陣列位置,然後再在對應的連結串列中的結點依次呼叫 equals() 方法來判斷結點中的物件是否是你想要的物件。
由於我們只重寫了 equals 方法並沒有重寫 hashcode 方法,所以兩篇文章的 hashcode 值不一樣,這樣對映到陣列的位置就不一樣,呼叫 set.contains(article1) 方法時,在雜湊表中的情況可能如下圖所示:
article 物件被對映到了陣列下標為 0 的位置,article1 物件被對映到了陣列下標為 6 的位置,所以沒有找到返回 False。既然只重寫 equals 方法不行,那麼我們把 hashcode 方法也重寫了。
2、重寫 hashcode 方法
跟 equals 方法一樣,我們也使用 idea 編輯器幫我們生成的 hashcode 方法,只需要做稍微的改動就可以,具體 hashcode 程式碼如下:
@Override
public int hashCode() {
return Objects.hash(url);
}
重寫好 hashcode 方法之後,再一次執行 Main 方法,這次得到的結果為 True,這會就是我們想要的結果了。重寫 equals 和 hashcode 方法之後,在雜湊表中的查詢如下圖所示:
首先 article1 物件也會被對映到陣列下標為 1 的位置,在陣列下標為 1 的位置存在 article 資料節點,所以會執行 article1.equals(article) 命令,因為我們重寫了 Article 物件的 equals 方法,這個是否會判斷兩個 Article 物件的 url 屬性是否相等,如果相等就返回 True,在這裡顯然是相等的,所以這裡就返回 True,得到我們想要的結果。
如何編寫 equals 和 hashcode 方法?
需要自己重寫 equals 方法?好的,我這就重寫,噼裡啪啦的敲出了下面這段程式碼:
public boolean equals(Article o) {
if (this == o) return true;
if (o == null || !(o instanceof Article)) return false;
return o.url.equals(url);
}
這樣寫對嗎?雖然裡面的邏輯看上的沒什麼問題,但是 equals 方法的引數變成了Article。 其實你這跟重寫 equals 方法沒有半毛線關係,這完全是重新定義了一個引數型別為 Article 的 equals 方法,並沒有去覆蓋 Object 類中的 equals 方法。
那該如何重寫 equals 方法呢?其實 equals 方法是有通用規定的,當你重寫 equals 方法時,你就需要重寫 equals 方法的通用約定,在 Object 中有如下規範: equals 方法實現了一個等價關係(equivalence relation)。它有以下這些屬性:
- 自反性:對於任何非空引用 x,x.equals(x) 必須返回 true
- 對稱性:對於任何非空引用 x 和 y,如果且僅當 y.equals(x) 返回 true 時 x.equals(y) 必須返回 true
- 傳遞性:對於任何非空引用 x、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,則 x.equals(z) 必須返回 true
- 一致性:對於任何非空引用 x 和 y,如果在 equals 比較中使用的資訊沒有修改,則 x.equals(y) 的多次呼叫必須始終返回 true 或始終返回 false
- 非空性:對於任何非空引用 x,x.equals(null) 必須返回 false
現在我們已經知道了寫 equals 方法的通用約定,那我們就參照重寫 equals 方法的通用約定,再一次來重寫 Article 物件的 equals() 方法。程式碼如下:
// 使用 @Override 標記,這樣就可以避免上面的錯誤
@Override
public boolean equals(Object o) {
// 1、判斷是否等於自身
if (this == o) return true;
// 2、判斷 o 物件是否為空 或者型別是否為 Article
if (o == null || !(o instanceof Article)) return false;
// 3、引數型別轉換
Article article = (Article) o;
// 4、判斷兩個物件的 url 是否相等
return article.url.equals(url);
}
這一次我們使用了 @Override 標記,這樣就可以避免我們上一個重寫的錯誤,因為父類中並沒有引數為 Article 的方法,所以編譯器會報錯,這對程式設計師來說是非常友好的。接下來我們進行了 自反性、非空性的驗證,最後判斷兩個物件的 url 是否相等。這個 equals 方法就比上面那個要好很多,基本上沒什麼大毛病了。
在 effective-java 書中總結了一套編寫高質量 equals 方法的配方,配方如下:
- 1、使用 == 運算子檢查引數是否為該物件的引用。如果是,返回 true。
- 2、使用 instanceof 運算子來檢查引數是否具有正確的型別。 如果不是,則返回 false。
- 3、引數轉換為正確的型別。因為轉換操作在 instanceof 中已經處理過,所以它肯定會成功。
- 4、對於類中的每個「重要」的屬性,請檢查該引數屬性是否與該物件對應的屬性相匹配。
我們已經瞭解了怎麼重寫 equals 方法了,接下來就一起了解如何重寫 hashcode 方法,我們知道 hashcode 方法返回的是一個 int 型別的方法,那好辦呀,像下面這樣重寫就行了
@Override
public int hashCode() {
return 1;
}
這樣寫對嗎?對錯先不管,我們先來看一下 hashcode 在 Object 中的規定:
- 1、當在一個應用程式執行過程中,如果在 equals 方法比較中沒有修改任何資訊,在一個物件上重複呼叫 hashCode 方法時,它必須始終返回相同的值。從一個應用程式到另一個應用程式的每一次執行返回的值可以是不一致的。
- 2、如果兩個物件根據 equals(Object) 方法比較是相等的,那麼在兩個物件上呼叫 hashCode 就必須產生的結果是相同的整數。
- 3、如果兩個物件根據 equals(Object) 方法比較並不相等,則不要求在每個物件上呼叫 hashCode 都必須產生不同的結果。
照 hashcode 規定來看,這樣寫似乎也沒什麼問題,但是你應該知道雜湊表,如果這樣寫的話,對於HashMap 和 HashSet 等散列表來說,直接把它們廢掉了,在哈列表中,元素對映到陣列的哪個位置靠 hashcode 決定,而我們的 hashcode 始終返回 1 ,這樣的話,每個元素都會對映到相同的位置,散列表也會退化成連結串列。
結合 hashcode 的規範和散列表來看,要重寫出一個高質量的 hashcode 方法,就需要儘可能保證每個元素產生不同的 hashcode 值,在 JDK 中,每個引用型別都重寫了 hashcode 函式,我們看看 String 類中的 hashcode 是如何重寫的:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
這個 hashcode 方法寫的還是非常好的,我個人比較喜歡用官方的東西,我覺得他們考慮的肯定比我們多很多,所以我們 Article 類的 hashcode 方法就可以這樣寫
/**
* 重寫 hashcode方法,根據url返回hash值
* @return
*/
@Override
public int hashCode() {
return url.hashCode();
}
我們直接呼叫 String 物件的 hashcode 方法。到此我們的 equals 方法和 hashcode 方法都重寫完了,最後以 effective-java 裡面的一段總結結尾吧。
- 1、當重寫 equals 方法時,同時也要重寫 hashCode 方法
- 2、不要讓 equals 方法試圖太聰明。
- 3、在 equal 時方法宣告中,不要將引數 Object 替換成其他型別。
文章不足之處,望大家多多指點,共同學習,共同進步
最後
打個小廣告,歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,一起進步吧。
相關推薦
如何編寫出高質量的 equals 和 hashcode 方法?
什麼是 equals 和 hashcode 方法? 這要從 Object 類開始說起,我們知道 Object 類是 Java 的超類,每個類都直接或者間接的繼承了 Object 類,在 Object 中提供了 8 個基本的方法,equals 方法和 hashcode 方法就是其中的兩個。 equals 方法:
java :equals()和hashcode()方法的結合使用
pro 解決 java lin testin ren use main 結合 哈希表這個數據結構想必大多數人都不陌生,而且在很多地方都會利用到hash表來提高查找效率。在Java的Object類中有一個方法: 1 public native int hashCo
如何正確的重寫equals() 和 hashCode()方法
來看 oar gpo ati sdn copy sys == -m 比較兩個Java對象時, 我們需要覆蓋equals和 hashCode。 [java] view plain copy public class User{ privat
重寫equals() 和 hashCode()方法
@override and 操作 ole obj 我們 override mil 想法 去重操作時, 有時候往Set集合存放對象User,我們User類的字段太多時,比如有50個字段, 判斷兩個User對象相同,不需要判斷它們所有字段都相同,只需要判斷它們的某幾個字段相
重寫equals和hashCode方法原因
註釋 ons tag with spa detail 代碼 auth tail 轉載自:https://blog.csdn.net/zh_w_h163/article/details/11907869 在程序中,我們習慣使用equals方法來比較兩個對象,繼承自Object
List集合去重的一些方法(常規遍歷、Set去重、java8 stream去重、重寫equals和hashCode方法)
利用 src false java8 see eat 基本 style ceo 1. 常規元素去重 碰到List去重的問題,除了遍歷去重,我們常常想到利用Set集合不允許重復元素的特點,通過List和Set互轉,來去掉重復元素。 // 遍歷後判斷賦給另一個list集
String的equals和hashCode方法
als bool str ins -- pan 因此 string類 ash 對於判斷對象是否相等,肯定需要重寫它的equals和hashCode方法。不然使用默認的方法只會比較地址,因此會出現錯誤。 以String類為例,且看它的equals方法 publ
為什麼要重寫equals() 和 hashcode() 方法
重寫equals() 是為了保證比如new ArrayList().contains(Object)的基於equals() 做比較的可用性 重寫hashcode() 是為了保證比如new hashMap().put(Object)的基於hashcode() 做key值的可用性 &n
為什麽要重寫equals和hashcode方法
.get hash -a style radi his string 了解 com equals hashcode 當新建一個java類時,需要重寫equals和hashcode方法,大家都知道!但是,為什麽要重寫呢? 需要保證對象調用equals方法為tru
為什麼要重寫equals和HashCode方法
同事問我的題,說我答不上來沒法留在公司工作,答的不是很好,特此整理一下。 對於這個問題,我覺得首先應該去思考的是原來的equals方法和HashCode方法是什麼樣的。 原生的hashCode值是根據記憶體地址換算出來的一個值。 原生的equals方法是嚴格判斷
Java中的equals和hashCode方法詳解
Java中的equals方法和hashCode方法是Object中的,所以每個物件都是有這兩個方法的,有時候我們需要實現特定需求,可能要重寫這兩個方法,今天就來介紹一些這兩個方法的作用。 equa
Java 覆蓋equals和hashCode方法
前言 覆蓋equals方法看起來似乎很簡單,但是有許多覆蓋方式會導致錯誤,並且後果非常嚴重,最容易避免這類問題的辦法就是不覆蓋equals方法。 什麼時候需要覆蓋equals方法?如果類具有自己特有的“邏輯相等”概念(不同於物件等同),而且超類還沒有覆蓋eq
java 重寫equals和hashCode方法原則
根據《effective java》第七條之規定:在改寫equals的時候遵守通用約定 當符合以下條件時不需要重寫equals方法: 1. 一個類的每一個例項本質上都是唯一的。 2. 不關心一個類是否提供了“邏輯相等”的測試功能 3.
重寫equals和hashcode方法來使兩個物件相等
環境 java:1.7 場景 最近在重構訊息系統,客戶端傳送的訂閱訊息,在後臺轉成一個Message類, 假設客戶端傳送了兩個一模一樣的訊息時,其生成的Message類也應該相等的; 這時就需要重寫equals和hashcode方法。 程式碼 Message類:
如何覆寫java中的equals和hashcode方法
這篇文章算是一個翻譯,原文在:點選開啟連結,但我並沒有一字不差全部翻譯,只是選出一些重點,大家有興趣可以看看原文。 Equals和 hashCode是java中一個物件的兩個基本方法和core java的重要組成部分。Equals用
使用HashSet儲存自定義類物件時為什麼要重寫equals和hashCode方法?
在Java集合的運用中,HashSet抽象類實現了Set介面,我們可以通過HashSet儲存Java中定義過的類建立的物件,當然也可以儲存我們自定義的類建立的物件。 但是在儲存自定義類建立的物件時,就會遇到實際問題導致的漏洞;首先,我們分析一下HashSet類中ad
Java-正確使用equals和hashCode方法
基本摘抄自Java 中正確使用 hashCode 和 equals 方法 hashCode()和equals()定義在Object類中,這個類是所有java類的基類,所以所有的java類都繼承這兩個方法。 1.equals equals要遵守的通用約定
向HashSet集合存入物件,去除重複元素(覆寫equals和hashCode方法)
import java.util.*; class Person { private String name; private int age; Person(String name,int a
String原始碼分析之equals和hashcode方法
1.說明 == :如果是非引用型別,則值相等;引用型別,則地址相同,也就是指向堆中相同的物件 equals:Object物件而言或者沒有重寫equals方法的類,等效於==;重寫了equals方法則按子類的方法來比較 2.String的equals方法
Map中的key和Set中的元素物件必須重寫equals和hashCode方法
【強制】關於hashCode和equals的處理,遵循如下規則: 1) 只要重寫 equals,就必須重寫hashCode。 2) 因為 Set儲存的是不重複的物件,依據 hashCode和equals進行判斷,所以 Set儲存的 物件必須重寫這兩個方法。 3) 如果自定義物