Java基礎(chǔ)之淺談hashCode()和equals()
寫在前面
其實(shí)很早我就注意到阿里巴巴Java開發(fā)規(guī)范有一句話:只要重寫 equals,就必須重寫 hashCode
。
我想很多人都會(huì)問為什么,所謂知其然知其所以然
,對(duì)待知識(shí)不單止知道結(jié)論還得知道原因。
hashCode方法
hashCode()方法的作用是獲取哈希碼,返回的是一個(gè)int整數(shù)
學(xué)過數(shù)據(jù)結(jié)構(gòu)的都知道,哈希碼的作用是確定對(duì)象在哈希表的索引下標(biāo)。比如HashSet和HashMap就是使用了hashCode方法確定索引下標(biāo)。如果兩個(gè)對(duì)象返回的hashCode相同,就被稱為“哈希沖突”。
equals方法
equals()方法的作用很簡(jiǎn)單,就是判斷兩個(gè)對(duì)象是否相等,equals()方法是定義在Object類中,而所有的類的父類都是Object,所以如果不重寫equals方法則會(huì)調(diào)用Object類的equals方法。
Object類的equals方法是用“”號(hào)進(jìn)行比較,在很多時(shí)候,因?yàn)樘?hào)比較的是兩個(gè)對(duì)象的內(nèi)存地址而不是實(shí)際的值,所以不是很符合業(yè)務(wù)要求。所以很多時(shí)候我們需要重寫equals方法,去比較對(duì)象中每一個(gè)成員變量的值是否相等。
問題來了
重寫equals()方法就可以比較兩個(gè)對(duì)象是否相等,為什么還要重寫hashcode()方法呢?
因?yàn)镠ashSet、HashMap底層在添加元素時(shí),會(huì)先判斷對(duì)象的hashCode是否相等,如果hashCode相等才會(huì)用equals()方法比較是否相等。換句話說,HashSet和HashMap在判斷兩個(gè)元素是否相等時(shí),會(huì)先判斷hashCode,如果兩個(gè)對(duì)象的hashCode不同則必定不相等。
下面我們做一個(gè)試驗(yàn),有一個(gè)User類,只重寫equals()方法,然后放到Set集合中去重。
public class User { private String id; private String name; private Integer age; public User(String id, String name, Integer age) { this.id = id; this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age); } //getter、setter、toString方法 }
然后我們循環(huán)創(chuàng)建10個(gè)成員變量的值都是一樣的User對(duì)象,最后放到Set集合中去重。
public static void main(String[] args) { List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { User user = new User("1", "張三", 18); list.add(user); } Set<User> set = new HashSet<>(list); for (User user : set) { System.out.println(user); } List<User> users = list.stream().distinct().collect(Collectors.toList()); System.out.println(users); }
按道理我們預(yù)期會(huì)去重,只剩下一個(gè)“張三”的user,但實(shí)際上因?yàn)闆]有重寫hashCode方法,所以沒有去重。
接著我們?cè)赨ser類里面重寫一些hashCode方法再試試,其他不變。
public class User { //其他不變 //重寫hashCode方法 @Override public int hashCode() { return Objects.hash(id, name, age); } }
再運(yùn)行,結(jié)果正確。
究其原因在于HashSet會(huì)先判斷hashCode是否相等,如果hashCode不相等就直接認(rèn)為兩個(gè)對(duì)象不相等,不會(huì)再用equals()比較了。我們不妨看看重寫hashCode方法和不重寫hashCode方法的哈希碼。
這是不重寫hashCode方法的情況,每個(gè)user對(duì)象的哈希碼都不一樣,所以HashSet會(huì)認(rèn)為都不相等。
這是重寫hashCode方法的情況,因?yàn)槭怯脤?duì)象所有的成員變量的值計(jì)算出的哈希碼,所以只要兩個(gè)對(duì)象的成員變量都是相等的,則生成的哈希碼是相同的。
那么有些人看到這里,就會(huì)問,如果兩個(gè)對(duì)象返回的哈希碼都是一樣的話,是不是就一定相等?
答案是不一定的,因?yàn)镠ashSet、HashMap判斷哈希碼相等后還會(huì)再用equals()方法判斷。
總而言之:
- 哈希碼不相等,則兩個(gè)對(duì)象一定不相同。
- 哈希碼相等,兩個(gè)對(duì)象不一定相同。
- 兩個(gè)對(duì)象相同,則哈希碼和值都一定相等。
總結(jié)
所以回到開頭講的那句,只要重寫 equals,就必須重寫 hashCode
,這是一個(gè)很重要的細(xì)節(jié),如果不注意的話,很容易發(fā)生業(yè)務(wù)上的錯(cuò)誤。
特別是有時(shí)候我們明明用了HashSet,distinct()去重,但是就是不生效,這時(shí)應(yīng)該回頭看看重寫了equals()和hashCode()方法了嗎?
到此這篇關(guān)于Java基礎(chǔ)之淺談hashCode()和equals()的文章就介紹到這了,更多相關(guān)hashCode()和equals()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring中的DeferredImportSelector實(shí)現(xiàn)詳解
這篇文章主要介紹了Spring中的DeferredImportSelector實(shí)現(xiàn)詳解,兩個(gè)官方的實(shí)現(xiàn)類AutoConfigurationImportSelector和ImportAutoConfigurationImportSelector都是Spring Boot后新增的實(shí)現(xiàn),需要的朋友可以參考下2024-01-01Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表圖解
這篇文章主要為大家詳細(xì)介紹了Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中圖解分析的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Java中的InputStreamReader和OutputStreamWriter源碼分析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文通過示例代碼給大家解析了Java中的InputStreamReader和OutputStreamWriter知識(shí),需要的的朋友參考下吧2017-05-05Java設(shè)計(jì)模式之備忘錄模式實(shí)現(xiàn)對(duì)象狀態(tài)的保存和恢復(fù)
本文介紹Java設(shè)計(jì)模式之備忘錄模式,該模式可以實(shí)現(xiàn)對(duì)象狀態(tài)的保存和恢復(fù)。通過詳細(xì)講解備忘錄模式的原理、實(shí)現(xiàn)方法和應(yīng)用場(chǎng)景,幫助讀者深入理解該設(shè)計(jì)模式,并提供示例代碼和技巧,便于讀者實(shí)際應(yīng)用2023-04-04Java創(chuàng)建可執(zhí)行JAR文件的多種方式
本文主要介紹了Java創(chuàng)建可執(zhí)行JAR文件的多種方式,使用JDK的jar工具、IDE、Maven和Gradle來創(chuàng)建和配置可執(zhí)行JAR文件,具有一定的參考價(jià)值,感興趣的可以了解一下2024-07-07