上一篇文章 如何妙用 Spring 數據綁定? ,靈魂追問 環節留下了一個有關 equals 和 hashcode 問題 。基礎面試常常會碰到與之相關的問題,這不是一個複雜的問題,但不少朋友都苦於說明他們兩者的關係和約束,因而寫本文作單獨說明,本篇文章將按部就班 ( 經過舉例,讓記憶與理解更輕鬆 ) 說明這些讓你有些苦惱的問題,Let's go .......html
==
比較的是對象地址,equals
比較的是對象值
先來看一看 Object
類中 equals
方法:java
public boolean equals(Object obj) { return (this == obj); }
咱們看到 equals
方法一樣是經過 ==
比較對象地址,並無幫咱們比較值。Java 世界中 Object
絕對是"老祖宗" 的存在,==
號咱們沒辦法改變或重寫。但 equals
是方法,這就給了咱們重寫 equals
方法的可能,讓咱們實現其對值的比較:面試
@Override public boolean equals(Object obj) { //重寫邏輯 }
新買的電腦,每一個電腦都有惟一的序列號,一般狀況下,兩個如出一轍的電腦放在面前,你會說因爲序列號不同,這兩個電腦不同嗎?spring
若是咱們要說兩個電腦同樣,一般是比較其「品牌/尺寸/配置 」(值) ,好比這樣:segmentfault
@Override public boolean equals(Object obj) { return 品牌相等 && 尺寸相等 && 配置相等 }
當遇到如上場景時,咱們就須要重寫 equals
方法。這就解釋了 Java 世界爲何有了 ==
還有equals
這個問題了.ide
equals
相等 和 hashcode
相等問題關於兩者,你常常會碰到下面的兩個問題:測試
equals
相等,那他們 hashCode
相等嗎?hashCode
相等,那他們 equals
相等嗎?爲了說明上面兩個問題的結論,這裏舉一個不太恰當的例子,只爲方便記憶,咱們將 equals
比做一個單詞的拼寫;hashCode
比做一個單詞的發音,在相同語境下:this
sea / sea 「大海」,兩個單詞拼寫同樣,因此
equals
相等,他們讀音/siː/
也同樣,因此hashCode
就相等,這就回答了第一個問題:spa兩個對象
equals
相等,那他們hashCode
必定也相等sea / see 「大海/看」,兩個單詞的讀音
/siː/
同樣,顯然單詞是不同的,這就回答了第二個問題:code兩個對象
hashCode
相等,那他們equals
不必定相等
查看 Object
類的 hashCode
方法:
public native int hashCode();
繼續查看該方法的註釋,明確寫明關於該方法的約束
其實在這個結果的背後,還有的是關於重寫 equals
方法的約束
equals
有哪些約束?關於重寫 equals
方法的約束,一樣在該方法的註釋中寫的很清楚了,我在這裏再說明一下:
赤橙紅綠青藍紫,七彩以色列;哆來咪發唆拉西, 一曲安哥拉 ,這些規則不是用來背誦的,只是在你須要重寫 equals
方法時,打開 JDK 查看該方法,按照準則重寫就好
hashCode
?爲了比較值,咱們重寫 equals
方法,那何時又須要重寫 hashCode
方法呢?
一般只要咱們重寫equals
方法就要重寫hashCode
方法
爲何會有這樣的約束呢?按照上面講的原則,兩個對象 equals
相等,那他們的 hashCode
必定也相等。若是咱們只重寫 equals
方法而不重寫 hashCode
方法,看看會發生什麼,舉個例子來看:
定義學生類,並經過 IDE 只幫咱們生成 equals
方法:
public class Student { private String name; private int age; @Override public 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)的結果是:" + 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());
查看運行結果:
student1.equals(student2)的結果是:true Student Set 集合長度是:2 Student Map 集合長度是:2
很顯然,按照集合 Set 和 Map 加入元素的標準來看,student1 和 student2 是兩個對象,由於在調用他們的 put (Set add 方法的背後也是 HashMap 的 put)方法時, 會先判斷 hash 值是否相等,這個小夥伴們打開 JDK 自行查看吧
因此咱們繼續重寫 Student 類的 hashCode
方法:
@Override public int hashCode() { return Objects.hash(name, age); }
從新運行上面的測試,查看結果:
student1.equals(student2)的結果是:true Student Set 集合長度是:1 Student Map 集合長度是:1
獲得咱們預期的結果,這也就是爲何一般咱們重寫 equals
方法爲何最好也重寫 hashCode
方法的緣由
@EqualsAndHashCode
註解,而沒有拆分紅 @Equals 和 @HashCode 兩個註解,想了解更多 Lombok 的內容,也能夠查看我以前寫的文章 Lomok 使用詳解 以上兩點都是隱形的規範約束,但願你們也嚴格遵照這個規範,以防帶來沒必要要的麻煩,記憶的方式有多樣,若是記不住這個文字約束,腦海中記住上面的圖你也就懂了
hashCode
爲何總有 31 這個數字?細心的朋友可能注意到,我上面重寫 hashCode
的方法很簡答, 就是用了 Objects.hash
方法,進去查看裏面的方法:
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 數據綁定? 文章末尾提到的在 HandlerMethodArgumentResolverComposite
類中有這樣一個成員變量:
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
Map 的 key 是 MethodParameter
,根據咱們上面的分析,這個類必定也會重寫 equals
和 hashCode
方法,進去查看發現,hashCode 的計算也用到了 31 這個數字
@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 這個數字呢?我在網上看到一篇不錯的文章,分享給你們,做爲科普,能夠簡單查看一下:
String hashCode 方法爲何選擇數字31做爲乘子
若是還對equals
和 hashCode
關係及約束含混,咱們只須要按照上述步驟逐步回憶便可,更好的是直接查看 JDK 源碼;另外拿出實際的例子來反推驗證是很是好的辦法。若是你還有相關疑問,也能夠留言探討.
equals
方法,你還知道哪些狀況不必重寫 equals
方法嗎?歡迎關注個人公衆號 「日拱一兵」,趣味原創解析Java技術棧問題,將複雜問題簡單化,將抽象問題圖形化落地
若是對個人專題內容感興趣,或搶先看更多內容,歡迎訪問個人博客 dayarch.top