為什么在重寫 equals方法的同時必須重寫 hashcode方法
我們都知道Java語言是完全面向?qū)ο蟮?,在java中,所有的對象都是繼承于Object類。
其 equals 方法比較的是兩個對象的引用指向的地址,hashcode 是一個本地方法,返回的是對象地址值。Ojbect類中有兩個方法equals、hashCode,這兩個方法都是用來比較兩個對象是否相等的。
為何重寫 equals方法的同時必須重寫 hashcode方法呢
可以這樣理解:重寫了 equals 方法,判斷對象相等的業(yè)務(wù)邏輯就變了,類的設(shè)計者不希望通過比較內(nèi)存地址來比較兩個對象是否相等,而 hashcode 方法繼續(xù)按照地址去比較也沒有什么意義了,索性就跟著一起變吧。
還有一個原因來源于集合。下面慢慢說~
舉個例子:
在學(xué)校中,是通過學(xué)號來判斷是不是這個人的。
下面代碼中情景為學(xué)籍錄入,學(xué)號 123 被指定給學(xué)生 Tom,學(xué)號 456 被指定給學(xué)生 Jerry,學(xué)號 123 被失誤指定給 Lily。而在錄入學(xué)籍的過程中是不應(yīng)該出現(xiàn)學(xué)號一樣的情況的。
根據(jù)情景需求是不能添加重復(fù)的對象,可以通過 HashSet 實現(xiàn)。
public class Test { public static void main(String[] args) { Student stu = new Student(123,"Tom"); HashSet<Student> set = new HashSet<>(); set.add(stu); set.add(new Student(456, "Jerry")); set.add(new Student(123, "Lily")); Iterator<Student> iterator = set.iterator(); while (iterator.hasNext()) { Student student = iterator.next(); System.out.println(student.getStuNum() + " --- " + student.getName()); } } }; class Student { private int stuNum; private String name; public Student(int stuNum,String name){ this.stuNum = stuNum; this.name = name; } public int getStuNum() { return stuNum; } public String getName() { return name; } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj instanceof Student){ if(this.getStuNum()==((Student)obj).getStuNum()) return true; } return false; } }
輸出為:
123 --- Lily
456 --- Jerry
123 --- Tom
根據(jù)輸出我們發(fā)現(xiàn),再次將學(xué)號 123 指定給 Lily 居然成功了。到底哪里出了問題呢?
我們看一下 HashSet 的 add 方法:
public boolean add(E e) { return map.put(e, PRESENT)==null; }
其實 HashSet 是通過 HashMap 實現(xiàn)的,由此我們追蹤到 HashMap 的 put 方法:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
1.根據(jù) key,也就是 HashSet 所要添加的對象,得到 hashcode,由 hashcode 做特定位運算得到 hash 碼;
2.利用 hash 碼定位找到數(shù)組下標(biāo),得到鏈表的鏈?zhǔn)祝?/p>
3.遍歷鏈表尋找有沒有相同的 key,判斷依據(jù)是 e.hash == hash && ((k = e.key) == key || key.equals(k))。在add Lily 的時候,由于重寫了 equals 方法,遍歷到 Tom 的時候第二個條件應(yīng)該是 true;但是因為 hashcode 方法還是使用父類的,故而 Tom 和 Lily的 hashcode 不同也就是 hash 碼不同,第一個條件為 false。這里得到兩個對象是不同的所以 HashSet 添加 Lily 成功。
總結(jié)出來原因是沒有重寫 hashcode 方法,下面改造一下:
public class Test { public static void main(String[] args) { Student stu = new Student(123,"Tom"); HashSet<Student> set = new HashSet<>(); set.add(stu); set.add(new Student(456, "Jerry")); set.add(new Student(123, "Lily")); Iterator<Student> iterator = set.iterator(); while (iterator.hasNext()) { Student student = iterator.next(); System.out.println(student.getStuNum() + " --- " + student.getName()); } } }; class Student { private int stuNum; private String name; public Student(int stuNum,String name){ this.stuNum = stuNum; this.name = name; } public int getStuNum() { return stuNum; } public String getName() { return name; } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj instanceof Student){ if(this.getStuNum()==((Student)obj).getStuNum()) return true; } return false; } @Override public int hashCode() { return getStuNum(); } }
輸出:
456 --- Jerry
123 --- Tom
重寫了 hashcode 方法返回學(xué)號。OK,大功告成。
有人可能會奇怪,e.hash == hash && ((k = e.key) == key || key.equals(k)) 這個條件是不是有點復(fù)雜了,我感覺只使用 equals 方法就可以了啊,為什么要多此一舉去判斷 hashcode 呢?
因為在 HashMap 的鏈表結(jié)構(gòu)中遍歷判斷的時候,特定情況下重寫的 equals 方法比較對象是否相等的業(yè)務(wù)邏輯比較復(fù)雜,循環(huán)下來更是影響查找效率。所以這里把 hashcode 的判斷放在前面,只要 hashcode 不相等就玩兒完,不用再去調(diào)用復(fù)雜的 equals 了。很多程度地提升 HashMap 的使用效率。
所以重寫 hashcode 方法是為了讓我們能夠正常使用 HashMap 等集合類,因為 HashMap 判斷對象是否相等既要比較 hashcode 又要使用 equals 比較。而這樣的實現(xiàn)是為了提高 HashMap 的效率。
相關(guān)文章
Java實現(xiàn)Json字符串與Object對象相互轉(zhuǎn)換的方式總結(jié)
這篇文章主要介紹了Java實現(xiàn)Json字符串與Object對象相互轉(zhuǎn)換的方式,結(jié)合實例形式總結(jié)分析了java基于Json-Lib、Org.Json、Jackson、Gson、FastJson五種方式轉(zhuǎn)換json類型相關(guān)操作技巧,需要的朋友可以參考下2019-03-03詳解基于java的Socket聊天程序——初始設(shè)計(附demo)
本篇文章主要介紹了Socket聊天程序——初始設(shè)計(附demo),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12RocketMQ線程池創(chuàng)建實現(xiàn)原理詳解
這篇文章主要為大家介紹了RocketMQ線程池創(chuàng)建實現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12mybatis源碼解讀-Java中executor包的語句處理功能
這篇文章主要介紹了Java中executor包的語句處理功能,在mybatis映射文件中傳參數(shù),主要用到#{}或者${},下文圍繞相關(guān)資料展開詳細(xì)內(nèi)容,需要的小伙伴可以參考一下2022-02-02Java獲取當(dāng)?shù)氐娜粘鋈章鋾r間代碼分享
這篇文章主要介紹了Java獲取當(dāng)?shù)氐娜粘鋈章鋾r間代碼分享,國外猿友寫的一個類,需要的朋友可以參考下2014-06-06java客戶端線上Apollo服務(wù)端的實現(xiàn)
這篇文章主要介紹了java客戶端線上Apollo服務(wù)端的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08