為何修改equals方法時(shí)還要重寫(xiě)hashcode方法的原因分析
為何修改equals方法時(shí)還要重寫(xiě)hashcode方法
雖然在實(shí)際開(kāi)發(fā)中,我們已經(jīng)使用到散列集合(如HashMap),或也單獨(dú)學(xué)過(guò)散列(Hash)。
但是也會(huì)有很多人像我一樣,看到有些時(shí)候別人寫(xiě)的pojo中有對(duì)對(duì)象內(nèi)hashcode函數(shù)做一個(gè)重寫(xiě),這就讓我重新思考為什么要這么做? 下面就讓我和你一起去探索一下吧!
Hash是什么?
Hash就是上文說(shuō)到的散列,是把任意長(zhǎng)度的輸入(又叫做預(yù)映射pre-image)通過(guò)散列算法變換成固定長(zhǎng)度的輸出,該輸出就是散列值。它的理論時(shí)間復(fù)雜度是可以達(dá)到O(1),但一般來(lái)說(shuō),這個(gè)散列函數(shù)是極難設(shè)計(jì)的。說(shuō)到散列值,就是通過(guò)散列函數(shù)轉(zhuǎn)化出來(lái)的:
如果兩個(gè)散列值是不一樣y(x1)!=y(x2),那么這兩個(gè)散列值的原始輸入一定是不一樣的。
如果兩個(gè)散列值出現(xiàn)了相等,那么并不代碼這兩個(gè)散列值的原始輸入一定是一樣的,可能是屬于哈希碰撞(不同關(guān)鍵字經(jīng)過(guò)散列變換結(jié)果是一樣的的現(xiàn)象);
對(duì)于哈希函數(shù)有哪些我也不再介紹,想了解可以直接去查散列函數(shù)的。
Hashcode作用
很多情況下我們也許都會(huì)用到hash表來(lái)做提高查詢(xún)效率,那么這個(gè)hash表是如何提高效率的?其實(shí)就是基于上面所說(shuō)的散列函數(shù),根據(jù)設(shè)計(jì)的散列函數(shù),我們對(duì)于每一個(gè)關(guān)鍵字都有唯一的散列值,那么就能夠直接根據(jù)這個(gè)散列值直接就能找到元素在集合中的位置,從而獲得其值,這對(duì)于集合的一個(gè)個(gè)對(duì)象進(jìn)行比較來(lái)說(shuō),是提高了很多的。

通過(guò)以上操作,我們很容易就能理解為啥散列技術(shù)在查詢(xún)的復(fù)雜度是能達(dá)到O(1).
但是一般來(lái)說(shuō)java都會(huì)內(nèi)置了hashcode的實(shí)現(xiàn),那為什么在寫(xiě)對(duì)象的時(shí)候,只要對(duì)equals進(jìn)行重寫(xiě),都推薦對(duì)hashcode進(jìn)行重寫(xiě)呢?
看HashCode的常規(guī)協(xié)定:
在 Java 應(yīng)用程序執(zhí)行期間,在同一對(duì)象上多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是對(duì)象上 equals 比較中所用的信息沒(méi)有被修改。從某一應(yīng)用程序的一次執(zhí)行到同一應(yīng)用程序的另一次執(zhí)行,該整數(shù)無(wú)需保持一致。
如果根據(jù) equals(Object) 方法,兩個(gè)對(duì)象是相等的,那么在兩個(gè)對(duì)象中的每個(gè)對(duì)象上調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。
以下情況不 是必需的:
如果根據(jù) equals(java.lang.Object) 方法,兩個(gè)對(duì)象不相等,那么在兩個(gè)對(duì)象中的任一對(duì)象上調(diào)用 hashCode 方法必定會(huì)生成不同的整數(shù)結(jié)果。但是,程序員應(yīng)該知道,為不相等的對(duì)象生成不同整數(shù)結(jié)果可以提高哈希表的性能。
實(shí)際上,由 Object 類(lèi)定義的 hashCode 方法確實(shí)會(huì)針對(duì)不同的對(duì)象返回不同的整數(shù)。(這一般是通過(guò)將該對(duì)象的內(nèi)部地址轉(zhuǎn)換成一個(gè)整數(shù)來(lái)實(shí)現(xiàn)的,但是 JavaTM 編程語(yǔ)言不需要這種實(shí)現(xiàn)技巧。)
當(dāng)equals方法被重寫(xiě)時(shí),通常有必要重寫(xiě) hashCode 方法,以維護(hù) hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對(duì)象必須具有相等的哈希碼。
根據(jù)以上知道,java內(nèi)部的一個(gè)實(shí)現(xiàn)是以地址來(lái)的,如果對(duì)equals進(jìn)行重寫(xiě)了,也就是對(duì)象你判斷相等時(shí)不再以java提供的方法,那么將來(lái)在使用hash表的時(shí)候,就會(huì)存在equals是相等的,但hashcode卻是不相等的!
所以建議:在修改equals的方法時(shí),記得修改hashcode方法!!!
下面做個(gè)小例子
/**
* @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());
}
運(yùn)行結(jié)果

嘗試將其放到set集合時(shí)

看到這結(jié)果顯然不是我們想要的,因?yàn)槲覂蓚€(gè)對(duì)象相等,其```hashcode也應(yīng)相等,然而結(jié)果卻是在不可重復(fù)的set集合中存了兩個(gè)對(duì)象,所以我們做一個(gè)改進(jìn),對(duì)User進(jìn)行重寫(xiě)hashcode``方法。
@Override
public int hashCode() {
return Objects.hash(getId()); //使用默認(rèn)的hash函數(shù)處理關(guān)鍵字,這里是ID,我們認(rèn)為Id相等的用戶(hù)其就是同一個(gè)用戶(hù)
}
然后看看set的結(jié)果:

的確符合我們預(yù)期結(jié)果。
基于以上的學(xué)習(xí),我們也基本了解為啥在修改equals方法時(shí)也要對(duì)hashcode進(jìn)行修改。
Java重寫(xiě)equals()方法的步驟
Java語(yǔ)言規(guī)范要求equals方法具有下面的特性:
- 自反性:對(duì)于任何非空引用x,x.equals(x)應(yīng)該返回true
- 對(duì)稱(chēng)性:對(duì)于任何引用x和y,當(dāng)且僅當(dāng)y.equals(x)返回true,x.equals(y)也應(yīng)該返回true
- 傳遞性:對(duì)于任何引用x和y和z,如果x.equal(y)返回true,y.equals(z)返回true,x.equals(z)也應(yīng)該返回true
- 一致性:如果x和y引用的對(duì)象沒(méi)有發(fā)生變化,反復(fù)調(diào)用x.equals(y)應(yīng)該返回同樣的結(jié)果
- 對(duì)于任意非空引用x,x.equals(null)應(yīng)該返回false
重寫(xiě)equals()方法的步驟:
顯式參數(shù)命名為otherObject,稍后需要將它轉(zhuǎn)換成另一個(gè)叫做other的變量
檢測(cè)this與otherObject是否引用同一個(gè)對(duì)象
if (this == otherObject)
return true;
檢測(cè)otherObject是否為null,是則返回false
if (this == null)
return false;
比較this與otherObject是否屬于同一個(gè)類(lèi)。如果equals的語(yǔ)義在每個(gè)子類(lèi)中有所改變,就使用getClass檢測(cè)
if (getClass() != otherObject.getClass())
return false;
如果所有的子類(lèi)都擁有統(tǒng)一的語(yǔ)義,就使用instanceof檢測(cè)
if (!(otherObject instanceof ClassName))
return false;
將otherObject轉(zhuǎn)換成相應(yīng)的類(lèi)類(lèi)型變量
ClassName other = (ClassName) otherObject
將other需要比較的域成員都進(jìn)行比較,只要有一個(gè)不同都返回false
需要注意的是,如果重新定義了equals()方法,就必須重新定義hashCode()方法,以便用戶(hù)可以將對(duì)象插入到散列表中。
equals()方法與hashCode()方法的定義必須保持一致,即如果equals()返回true,則2個(gè)對(duì)象的hashCode()必須具有相同的值。
重寫(xiě)equals()方法中有提到,我們需要將要比較的域成員都進(jìn)行比較,那么我們?cè)谥貙?xiě)hashCode()方法時(shí)可以將這些域成員的散列值組合起來(lái),這樣就能保證它與equals()方法具有一致性了。
假設(shè)需被比較的域成員為field_1、field_2與field_3,那么我們可以編寫(xiě)一下hashCode()方法:
public int hashCode() {
return Objects.hash(field_1, field_2, field_3);
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java如何將map數(shù)據(jù)存入到實(shí)體類(lèi)對(duì)象中
在Java編程中,經(jīng)常需要將Map集合中的數(shù)據(jù)轉(zhuǎn)換為實(shí)體類(lèi)對(duì)象,這可以通過(guò)反射機(jī)制實(shí)現(xiàn),即通過(guò)遍歷Map對(duì)象,使用反射根據(jù)鍵名對(duì)應(yīng)實(shí)體類(lèi)的屬性名,動(dòng)態(tài)調(diào)用setter方法將值設(shè)置到實(shí)體對(duì)象中,這樣的操作使得數(shù)據(jù)從Map結(jié)構(gòu)轉(zhuǎn)移到了具體的JavaBean中,便于后續(xù)的操作和管理2024-09-09
Java基本數(shù)據(jù)類(lèi)型與封裝類(lèi)型詳解(int和Integer區(qū)別)
這篇文章主要介紹了Java基本數(shù)據(jù)類(lèi)型與封裝類(lèi)型詳解(int和Integer區(qū)別) ,需要的朋友可以參考下2017-02-02
Struts中使用validate()輸入校驗(yàn)方法詳解
這篇文章主要介紹了Struts中使用validate()輸入校驗(yàn)方法,本文介紹的非常詳細(xì),具有參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-09-09
java中synchronized鎖的升級(jí)過(guò)程
這篇文章主要介紹了java中synchronized鎖的升級(jí)過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Java使用Thread創(chuàng)建多線(xiàn)程并啟動(dòng)操作示例
這篇文章主要介紹了Java使用Thread創(chuàng)建多線(xiàn)程并啟動(dòng)操作,結(jié)合實(shí)例形式分析了Java基于Thread類(lèi)的多線(xiàn)程定義與啟動(dòng)簡(jiǎn)單操作技巧,需要的朋友可以參考下2018-06-06
Java程序執(zhí)行過(guò)程及內(nèi)存機(jī)制詳解
本講將介紹Java代碼是如何一步步運(yùn)行起來(lái)的,還會(huì)介紹Java程序所占用的內(nèi)存是被如何管理的:堆、棧和方法區(qū)都各自負(fù)責(zé)存儲(chǔ)哪些內(nèi)容,感興趣的朋友跟隨小編一起看看吧2020-12-12
eclipse修改jvm參數(shù)調(diào)優(yōu)方法(2種)
本篇文章主要介紹了eclipse修改jvm參數(shù)調(diào)優(yōu)方法(2種),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02

