為何修改equals方法時還要重寫hashcode方法的原因分析
為何修改equals方法時還要重寫hashcode方法
雖然在實際開發(fā)中,我們已經(jīng)使用到散列集合(如HashMap),或也單獨學過散列(Hash)。
但是也會有很多人像我一樣,看到有些時候別人寫的pojo中有對對象內(nèi)hashcode函數(shù)做一個重寫,這就讓我重新思考為什么要這么做? 下面就讓我和你一起去探索一下吧!
Hash是什么?
Hash就是上文說到的散列,是把任意長度的輸入(又叫做預映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值。它的理論時間復雜度是可以達到O(1),但一般來說,這個散列函數(shù)是極難設計的。說到散列值,就是通過散列函數(shù)轉(zhuǎn)化出來的:
如果兩個散列值是不一樣y(x1)!=y(x2),那么這兩個散列值的原始輸入一定是不一樣的。
如果兩個散列值出現(xiàn)了相等,那么并不代碼這兩個散列值的原始輸入一定是一樣的,可能是屬于哈希碰撞(不同關鍵字經(jīng)過散列變換結(jié)果是一樣的的現(xiàn)象);
對于哈希函數(shù)有哪些我也不再介紹,想了解可以直接去查散列函數(shù)的。
Hashcode作用
很多情況下我們也許都會用到hash表來做提高查詢效率,那么這個hash表是如何提高效率的?其實就是基于上面所說的散列函數(shù),根據(jù)設計的散列函數(shù),我們對于每一個關鍵字都有唯一的散列值,那么就能夠直接根據(jù)這個散列值直接就能找到元素在集合中的位置,從而獲得其值,這對于集合的一個個對象進行比較來說,是提高了很多的。
通過以上操作,我們很容易就能理解為啥散列技術在查詢的復雜度是能達到O(1).
但是一般來說java都會內(nèi)置了hashcode的實現(xiàn),那為什么在寫對象的時候,只要對equals進行重寫,都推薦對hashcode進行重寫呢?
看HashCode的常規(guī)協(xié)定:
在 Java 應用程序執(zhí)行期間,在同一對象上多次調(diào)用 hashCode 方法時,必須一致地返回相同的整數(shù),前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的一次執(zhí)行到同一應用程序的另一次執(zhí)行,該整數(shù)無需保持一致。
如果根據(jù) equals(Object) 方法,兩個對象是相等的,那么在兩個對象中的每個對象上調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。
以下情況不 是必需的:
如果根據(jù) equals(java.lang.Object) 方法,兩個對象不相等,那么在兩個對象中的任一對象上調(diào)用 hashCode 方法必定會生成不同的整數(shù)結(jié)果。但是,程序員應該知道,為不相等的對象生成不同整數(shù)結(jié)果可以提高哈希表的性能。
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的,但是 JavaTM 編程語言不需要這種實現(xiàn)技巧。)
當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼。
根據(jù)以上知道,java內(nèi)部的一個實現(xiàn)是以地址來的,如果對equals進行重寫了,也就是對象你判斷相等時不再以java提供的方法,那么將來在使用hash表的時候,就會存在equals是相等的,但hashcode卻是不相等的!
所以建議:在修改equals的方法時,記得修改hashcode方法!!!
下面做個小例子
/** * @author: Kilig * @date: 2020/6/22 21:18 * @description: */ public class User { private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User)) return false; User user = (User) o; return getId() == user.getId(); } // @Override // public int hashCode() { // return Objects.hash(getId()); // } }
public static void main(String[] args) { User a=new User(); User b=new User(); a.setId(1); b.setId(1); System.out.println(a.equals(b)); System.out.println(a.hashCode() == b.hashCode()); }
運行結(jié)果
嘗試將其放到set集合時
看到這結(jié)果顯然不是我們想要的,因為我兩個對象相等,其```hashcode也應相等,然而結(jié)果卻是在不可重復的set集合中存了兩個對象,所以我們做一個改進,對User進行重寫hashcode``方法。
@Override public int hashCode() { return Objects.hash(getId()); //使用默認的hash函數(shù)處理關鍵字,這里是ID,我們認為Id相等的用戶其就是同一個用戶 }
然后看看set的結(jié)果:
的確符合我們預期結(jié)果。
基于以上的學習,我們也基本了解為啥在修改equals方法時也要對hashcode進行修改。
Java重寫equals()方法的步驟
Java語言規(guī)范要求equals方法具有下面的特性:
- 自反性:對于任何非空引用x,x.equals(x)應該返回true
- 對稱性:對于任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true
- 傳遞性:對于任何引用x和y和z,如果x.equal(y)返回true,y.equals(z)返回true,x.equals(z)也應該返回true
- 一致性:如果x和y引用的對象沒有發(fā)生變化,反復調(diào)用x.equals(y)應該返回同樣的結(jié)果
- 對于任意非空引用x,x.equals(null)應該返回false
重寫equals()方法的步驟:
顯式參數(shù)命名為otherObject,稍后需要將它轉(zhuǎn)換成另一個叫做other的變量
檢測this與otherObject是否引用同一個對象
if (this == otherObject) return true;
檢測otherObject是否為null,是則返回false
if (this == null) return false;
比較this與otherObject是否屬于同一個類。如果equals的語義在每個子類中有所改變,就使用getClass檢測
if (getClass() != otherObject.getClass()) return false;
如果所有的子類都擁有統(tǒng)一的語義,就使用instanceof檢測
if (!(otherObject instanceof ClassName)) return false;
將otherObject轉(zhuǎn)換成相應的類類型變量
ClassName other = (ClassName) otherObject
將other需要比較的域成員都進行比較,只要有一個不同都返回false
需要注意的是,如果重新定義了equals()方法,就必須重新定義hashCode()方法,以便用戶可以將對象插入到散列表中。
equals()方法與hashCode()方法的定義必須保持一致,即如果equals()返回true,則2個對象的hashCode()必須具有相同的值。
重寫equals()方法中有提到,我們需要將要比較的域成員都進行比較,那么我們在重寫hashCode()方法時可以將這些域成員的散列值組合起來,這樣就能保證它與equals()方法具有一致性了。
假設需被比較的域成員為field_1、field_2與field_3,那么我們可以編寫一下hashCode()方法:
public int hashCode() { return Objects.hash(field_1, field_2, field_3); }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
java如何將map數(shù)據(jù)存入到實體類對象中
在Java編程中,經(jīng)常需要將Map集合中的數(shù)據(jù)轉(zhuǎn)換為實體類對象,這可以通過反射機制實現(xiàn),即通過遍歷Map對象,使用反射根據(jù)鍵名對應實體類的屬性名,動態(tài)調(diào)用setter方法將值設置到實體對象中,這樣的操作使得數(shù)據(jù)從Map結(jié)構(gòu)轉(zhuǎn)移到了具體的JavaBean中,便于后續(xù)的操作和管理2024-09-09Java基本數(shù)據(jù)類型與封裝類型詳解(int和Integer區(qū)別)
這篇文章主要介紹了Java基本數(shù)據(jù)類型與封裝類型詳解(int和Integer區(qū)別) ,需要的朋友可以參考下2017-02-02Java使用Thread創(chuàng)建多線程并啟動操作示例
這篇文章主要介紹了Java使用Thread創(chuàng)建多線程并啟動操作,結(jié)合實例形式分析了Java基于Thread類的多線程定義與啟動簡單操作技巧,需要的朋友可以參考下2018-06-06eclipse修改jvm參數(shù)調(diào)優(yōu)方法(2種)
本篇文章主要介紹了eclipse修改jvm參數(shù)調(diào)優(yōu)方法(2種),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02