面試高頻考點:hashCode與equals
點擊關注公眾號,Java干貨及時送達
作者:隨身電源?
來源:https://juejin.cn/post/7011713684015677471
先來看阿里巴巴Java開發(fā)手冊中的一段話:
【強制】關于 hashCode 和 equals 的處理,遵循如下規(guī)則:1) 只要重寫 equals,就必須重寫 hashCode。2) 因為 Set 存儲的是不重復的對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的 對象必須重寫這兩個方法。3) 如果自定義對象作為 Map 的鍵,那么必須重寫 hashCode 和 equals。說明:String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象 作為 key 來使用。
它要求我們若是重寫equals方法則必須強制重寫hashCode,這是為何呢?
equals和hashCode方法
我們先來了解一下這兩個方法,它們都來自Object類,說明每一個類中都會有這么兩個方法,那它倆的作用是什么呢?
首先是equals方法,它是用來比較兩個對象是否相等。對于equals方法的使用,得分情況討論,若是子類重寫了equals方法,則將按重寫的規(guī)則進行比較,比如:
public?static?void?main(String[]?args)?{
????String?s?=?"hello";
????String?str2?=?"world";
????boolean?result?=?s.equals(str2);
????System.out.println(result);
}
來看看String類對equals方法的重寫:
public?boolean?equals(Object?anObject)?{
????if?(this?==?anObject)?{
????????return?true;
????}
????if?(anObject?instanceof?String)?{
????????String?anotherString?=?(String)anObject;
????????int?n?=?value.length;
????????if?(n?==?anotherString.value.length)?{
????????????char?v1[]?=?value;
????????????char?v2[]?=?anotherString.value;
????????????int?i?=?0;
????????????while?(n--?!=?0)?{
????????????????if?(v1[i]?!=?v2[i])
????????????????????return?false;
????????????????i++;
????????????}
????????????return?true;
????????}
????}
????return?false;
}
由此可知,String類調用equals方法比較的將是字符串的內容是否相等。又如:
public?static?void?main(String[]?args)?{
????Integer?a?=?500;
????Integer?b?=?600;
????boolean?result?=?a.equals(b);
????System.out.println(result);
}
觀察Integer類的實現(xiàn):
public?boolean?equals(Object?obj)?{
????if?(obj?instanceof?Integer)?{
????????return?value?==?((Integer)obj).intValue();
????}
????return?false;
}
它比較的仍然是值,然而若是沒有重寫equals方法:
@AllArgsConstructor
static?class?User?{
????private?String?name;
????private?Integer?age;
}
public?static?void?main(String[]?args)?{
????User?user?=?new?User("zs",?20);
????User?user2?=?new?User("zs",?20);
????boolean?result?=?user.equals(user2);
????System.out.println(result);
}
即使兩個對象中的值是一樣的,它也是不相等的,因為它執(zhí)行的是Object類的equals方法:
public?boolean?equals(Object?obj)?{
????return?(this?==?obj);
}
我們知道,對于引用類型,==比較的是兩個對象的地址值,所以結果為false,若是想讓兩個內容相同的對象在equals后得到true,則需重寫equals方法:
@AllArgsConstructor
static?class?User?{
????private?String?name;
????private?Integer?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(name,?user.name)?&&?Objects.equals(age,?user.age);
????}
}
再來聊一聊hashCode方法,它是一個本地方法,用來返回對象的hash碼值,通常情況下,我們都不會使用到這個方法,只有Object類的toString方法使用到了它:
public?String?toString()?{
????return?getClass().getName()?+?"@"?+?Integer.toHexString(hashCode());
}
為什么只要重寫了equals方法,就必須重寫hashCode
了解兩個方法的作用后,我們來解決本篇文章的要點,為什么只要重寫了equals方法,就必須重寫hashCode呢?這是針對一些使用到了hashCode方法的集合而言的,比如HashMap、HashSet等,先來看一個現(xiàn)象:
public?static?void?main(String[]?args)?{
????Map這段程序的輸出結果是:key--2,原因是HashMap中的key不能重復,當有重復時,后面的數據會覆蓋原值,所以HashMap中只有一個數據,那再來看下面一段程序:
@AllArgsConstructor
@ToString
static?class?User?{
????private?String?name;
????private?Integer?age;
}
public?static?void?main(String[]?args)?{
????Map它的結果應該是什么呢?是不是和剛才一樣,HashMap中也只有一條數據呢?可運行結果卻是這樣的:
EqualsAndHashCodeTest.User(name=zs,?age=20)--1
EqualsAndHashCodeTest.User(name=zs,?age=20)--2
這是為什么呢?這是因為HashMap認為這兩個對象并不相同,那我們就重寫equals方法:
@AllArgsConstructor
@ToString
static?class?User?{
????private?String?name;
????private?Integer?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(name,?user.name)?&&?Objects.equals(age,?user.age);
????}
}
public?static?void?main(String[]?args)?{
????Map運行結果:
true
EqualsAndHashCodeTest.User(name=zs,?age=20)--1
EqualsAndHashCodeTest.User(name=zs,?age=20)--2
兩個對象判斷是相同的,但HashMap中仍然存放了兩條數據,說明HashMap仍然認為這是兩個不同的對象。這其實涉及到HashMap底層的原理,查看HashMap的put方法:
public?V?put(K?key,?V?value)?{
????return?putVal(hash(key),?key,?value,?false,?true);
}
在存入數據之前,HashMap先對key調用了hash方法:
static?final?int?hash(Object?key)?{
????int?h;
????return?(key?==?null)???0?:?(h?=?key.hashCode())?^?(h?>>>?16);
}
該方法會調用key的hashCode方法并做右移、異或等操作,得到key的hash值,并使用該hash值計算得到數據的插入位置,如果當前位置沒有元素,則直接插入,如下圖所示:
既然兩個對象求得的hash值不一樣,那么就會得到不同的插入位置,由此導致HashMap最終存入了兩條數據。
接下來我們重寫User對象的hashCode和equals方法:
@AllArgsConstructor
@ToString
static?class?User?{
????private?String?name;
????private?Integer?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(name,?user.name)?&&?Objects.equals(age,?user.age);
????}
????@Override
????public?int?hashCode()?{
????????return?Objects.hash(name,?age);
????}
}
那么此時兩個對象計算得到的hash值就會相同:
當通過hash計算得到相同的插入位置后,user2便會發(fā)現(xiàn)原位置上已經有數據了,此時將觸發(fā)equals方法,對兩個對象的內容進行比較,若相同,則認為是同一個對象,再用新值覆蓋舊值,所以,我們也必須重寫equals方法,否則,HashMap始終會認為兩個new 出來的對象是不相同的,因為它倆的地址值不可能一樣。
由于String類重寫了hashCode和equals方法,所以,我們可以放心大膽地使用String類型作為HashMap的key。
在HashSet中,同樣會出現(xiàn)類似的問題:
@AllArgsConstructor
@ToString
static?class?User?{
????private?String?name;
????private?Integer?age;
}
public?static?void?main(String[]?args)?{
????Set對于內容相同的兩個對象,若是沒有重寫hashCode和equals方法,則HashSet并不會認為它倆重復,所以會將這兩個User對象都存進去。
總結
hashCode的本質是幫助HashMap和HashSet集合加快插入的效率,當插入一個數據時,通過hashCode能夠快速地計算插入位置,就不需要從頭到尾地使用equlas方法進行比較,但為了不產生問題,我們需要遵循以下的規(guī)則:
兩個相同的對象,其hashCode值一定相同 若兩個對象的hashCode值相同,它們也不一定相同
所以,如果不重寫hashCode方法,則會發(fā)生兩個相同的對象出現(xiàn)在HashSet集合中,兩個相同的key出現(xiàn)在Map中,這是不被允許的,綜上所述,在日常的開發(fā)中,只要重寫了equals方法,就必須重寫hashCode。
往 期 推 薦
1、Log4j2維護者吐槽沒工資還要挨罵,GO安全負責人建議開源作者向公司收費 2、太難了!讓程序員崩潰的8個瞬間 3、2021年程序員們都在用的神級數據庫 4、Windows重要功能被閹割,全球用戶怒噴數月后微軟終于悔改 5、牛逼!國產開源的遠程桌面火了,只有9MB 支持自建中繼器! 6、摔到老三的 Java,未來在哪? 7、真香!用 IDEA 神器看源碼,效率真高! 點分享
點收藏
點點贊
點在看





