hashcode 和 equals 的关系_Java equals 和 hashCode 的这几个问题可以说明白吗?
前言
上一篇文章 如何妙用 Spring 數(shù)據(jù)綁定? ,靈魂追問 環(huán)節(jié)留下了一個有關(guān) equals 和 hashcode 問題 。基礎(chǔ)面試經(jīng)常會碰到與之相關(guān)的問題,這不是一個復(fù)雜的問題,但很多朋友都苦于說明他們二者的關(guān)系和約束,于是寫本文做單獨說明,本篇文章將循序漸進(jìn) ( 通過舉例,讓記憶與理解更輕松 ) 說明這些讓你有些苦惱的問題,Let's go .......
面試問題
1. Java 里面有了 == 運算符,為什么還需要 equals ?
== 比較的是對象地址,equals 比較的是對象值先來看一看 Object 類中 equals 方法:
public boolean equals(Object obj) {return (this == obj); }我們看到 equals 方法同樣是通過 == 比較對象地址,并沒有幫我們比較值。Java 世界中 Object 絕對是"老祖宗" 的存在,== 號我們沒辦法改變或重寫。但 equals 是方法,這就給了我們重寫 equals 方法的可能,讓我們實現(xiàn)其對值的比較:
@Override public boolean equals(Object obj) {//重寫邏輯 }新買的電腦,每個電腦都有唯一的序列號,通常情況下,兩個一模一樣的電腦放在面前,你會說由于序列號不一樣,這兩個電腦不一樣嗎?
如果我們要說兩個電腦一樣,通常是比較其「品牌/尺寸/配置 」(值) ,比如這樣:
@Override public boolean equals(Object obj) {return 品牌相等 && 尺寸相等 && 配置相等 }當(dāng)遇到如上場景時,我們就需要重寫 equals 方法。這就解釋了 Java 世界為什么有了 == 還有equals 這個問題了.
2. equals相等 和 hashcode 相等問題
關(guān)于二者,你經(jīng)常會碰到下面的兩個問題:
- 兩個對象 equals 相等,那他們 hashCode 相等嗎?
- 兩個對象 hashCode 相等,那他們 equals 相等嗎?
為了說明上面兩個問題的結(jié)論,這里舉一個不太恰當(dāng)?shù)睦?#xff0c;只為方便記憶,我們將 equals 比作一個單詞的拼寫;hashCode 比作一個單詞的發(fā)音,在相同語境下:
sea / sea 「大海」,兩個單詞拼寫一樣,所以 equals 相等,他們讀音 /si?/ 也一樣,所以 hashCode 就相等,這就回答了第一個問題:兩個對象 equals 相等,那他們 hashCode 一定也相等sea / see 「大海/看」,兩個單詞的讀音 /si?/ 一樣,顯然單詞是不一樣的,這就回答了第二個問題:兩個對象 hashCode 相等,那他們 equals 不一定相等
查看 Object 類的 hashCode 方法:
public native int hashCode();繼續(xù)查看該方法的注釋,明確寫明關(guān)于該方法的約束
其實在這個結(jié)果的背后,還有的是關(guān)于重寫 equals 方法的約束
3. 重寫 equals 有哪些約束?
關(guān)于重寫 equals 方法的約束,同樣在該方法的注釋中寫的很清楚了,我在這里再說明一下:
赤橙紅綠青藍(lán)紫,七彩以色列;哆來咪發(fā)唆拉西, 一曲安哥拉 ,這些規(guī)則不是用來背誦的,只是在你需要重寫 equals 方法時,打開 JDK 查看該方法,按照準(zhǔn)則重寫就好
4. 什么時候需要我們重寫 hashCode?
為了比較值,我們重寫 equals 方法,那什么時候又需要重寫 hashCode 方法呢?
通常只要我們重寫 equals 方法就要重寫 hashCode 方法為什么會有這樣的約束呢?按照上面講的原則,兩個對象 equals 相等,那他們的 hashCode 一定也相等。如果我們只重寫 equals 方法而不重寫 hashCode 方法,看看會發(fā)生什么,舉個例子來看:
定義學(xué)生類,并通過 IDE 只幫我們生成 equals 方法:
public class Student {private String name;private int age;@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age &&Objects.equals(name, student.name);} }編寫測試代碼:
Student student1 = new Student(); student1.setName("日拱一兵"); student1.setAge(18);Student student2 = new Student(); student2.setName("日拱一兵"); student2.setAge(18);System.out.println("student1.equals(student2)的結(jié)果是:" + student1.equals(student2));Set<Student> students = new HashSet<Student>(); students.add(student1); students.add(student2); System.out.println("Student Set 集合長度是:" + students.size());Map<Student, java.lang.String> map = new HashMap<Student, java.lang.String>(); map.put(student1, "student1"); map.put(student2, "student2"); System.out.println("Student Map 集合長度是:" + map.keySet().size());查看運行結(jié)果:
student1.equals(student2)的結(jié)果是:true Student Set 集合長度是:2 Student Map 集合長度是:2很顯然,按照集合 Set 和 Map 加入元素的標(biāo)準(zhǔn)來看,student1 和 student2 是兩個對象,因為在調(diào)用他們的 put (Set add 方法的背后也是 HashMap 的 put)方法時, 會先判斷 hash 值是否相等,這個小伙伴們打開 JDK 自行查看吧
所以我們繼續(xù)重寫 Student 類的 hashCode 方法:
@Override public int hashCode() {return Objects.hash(name, age); }重新運行上面的測試,查看結(jié)果:
student1.equals(student2)的結(jié)果是:true Student Set 集合長度是:1 Student Map 集合長度是:1得到我們預(yù)期的結(jié)果,這也就是為什么通常我們重寫 equals 方法為什么最好也重寫 hashCode 方法的原因
- 如果你在使用 Lombok,不知道你是否注意到 Lombok 只有一個 @EqualsAndHashCode 注解,而沒有拆分成 @Equals 和 @HashCode 兩個注解,想了解更多 Lombok 的內(nèi)容,也可以查看我之前寫的文章 Lomok 使用詳解
- 另外通過 IDE 快捷鍵生成重寫方法時,你也會看到這兩個方法放在一起,而不是像 getter 和 setter 那樣分開
以上兩點都是隱形的規(guī)范約束,希望大家也嚴(yán)格遵守這個規(guī)范,以防帶來不必要的麻煩,記憶的方式有多樣,如果記不住這個文字約束,腦海中記住上面的圖你也就懂了
5. 重寫 hashCode 為什么總有 31 這個數(shù)字?
細(xì)心的朋友可能注意到,我上面重寫 hashCode的方法很簡答, 就是用了 Objects.hash 方法,進(jìn)去查看里面的方法:
public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result; }這里通過 31 來計算對象 hash 值
在 如何妙用 Spring 數(shù)據(jù)綁定? 文章末尾提到的在 HandlerMethodArgumentResolverComposite 類中有這樣一個成員變量:
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);Map 的 key 是 MethodParameter ,根據(jù)我們上面的分析,這個類一定也會重寫 equals 和 hashCode 方法,進(jìn)去查看發(fā)現(xiàn),hashCode 的計算也用到了 31 這個數(shù)字
@Override public boolean equals(Object other) {if (this == other) {return true;}if (!(other instanceof MethodParameter)) {return false;}MethodParameter otherParam = (MethodParameter) other;return (this.parameterIndex == otherParam.parameterIndex && getMember().equals(otherParam.getMember())); }@Override public int hashCode() {return (getMember().hashCode() * 31 + this.parameterIndex); }為什么計算 hash 值要用到 31 這個數(shù)字呢?我在網(wǎng)上看到一篇不錯的文章,分享給大家,作為科普,可以簡單查看一下: String hashCode 方法為什么選擇數(shù)字31作為乘子
總結(jié)
如果還對equals 和 hashCode 關(guān)系及約束含混,我們只需要按照上述步驟逐步回憶即可,更好的是直接查看 JDK 源碼;另外拿出實際的例子來反推驗證是非常好的辦法。如果你還有相關(guān)疑問,也可以留言探討.
靈魂追問
總結(jié)
以上是生活随笔為你收集整理的hashcode 和 equals 的关系_Java equals 和 hashCode 的这几个问题可以说明白吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么原因导致芯片短路_血压中的低压高是什
- 下一篇: coding怎么上传项目 mac_临近毕