欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

為什么在重寫 equals方法的同時必須重寫 hashcode方法

 更新時間:2016年07月27日 16:26:51   作者:屌絲的煩惱  
Object 類是所有類的父類,其 equals 方法比較的是兩個對象的引用指向的地址,hashcode 是一個本地方法,返回的是對象地址值。他們都是通過比較地址來比較對象是否相等的

我們都知道Java語言是完全面向?qū)ο蟮?,在java中,所有的對象都是繼承于Object類。
其 equals 方法比較的是兩個對象的引用指向的地址,hashcode 是一個本地方法,返回的是對象地址值。Ojbect類中有兩個方法equals、hashCode,這兩個方法都是用來比較兩個對象是否相等的。

為何重寫 equals方法的同時必須重寫 hashcode方法呢

可以這樣理解:重寫了 equals 方法,判斷對象相等的業(yè)務邏輯就變了,類的設計者不希望通過比較內(nèi)存地址來比較兩個對象是否相等,而 hashcode 方法繼續(xù)按照地址去比較也沒有什么意義了,索性就跟著一起變吧。

還有一個原因來源于集合。下面慢慢說~

舉個例子:

在學校中,是通過學號來判斷是不是這個人的。

下面代碼中情景為學籍錄入,學號 123 被指定給學生 Tom,學號 456 被指定給學生 Jerry,學號 123 被失誤指定給 Lily。而在錄入學籍的過程中是不應該出現(xiàn)學號一樣的情況的。

根據(jù)情景需求是不能添加重復的對象,可以通過 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),再次將學號 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ù)組下標,得到鏈表的鏈首;

3.遍歷鏈表尋找有沒有相同的 key,判斷依據(jù)是 e.hash == hash && ((k = e.key) == key || key.equals(k))。在add Lily 的時候,由于重寫了 equals 方法,遍歷到 Tom 的時候第二個條件應該是 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 方法返回學號。OK,大功告成。

有人可能會奇怪,e.hash == hash && ((k = e.key) == key || key.equals(k)) 這個條件是不是有點復雜了,我感覺只使用 equals 方法就可以了啊,為什么要多此一舉去判斷 hashcode 呢?

因為在 HashMap 的鏈表結(jié)構(gòu)中遍歷判斷的時候,特定情況下重寫的 equals 方法比較對象是否相等的業(yè)務邏輯比較復雜,循環(huán)下來更是影響查找效率。所以這里把 hashcode 的判斷放在前面,只要 hashcode 不相等就玩兒完,不用再去調(diào)用復雜的 equals 了。很多程度地提升 HashMap 的使用效率。

所以重寫 hashcode 方法是為了讓我們能夠正常使用 HashMap 等集合類,因為 HashMap 判斷對象是否相等既要比較 hashcode 又要使用 equals 比較。而這樣的實現(xiàn)是為了提高 HashMap 的效率。

相關(guān)文章

  • 一文帶你了解RabbitMQ消息轉(zhuǎn)換器

    一文帶你了解RabbitMQ消息轉(zhuǎn)換器

    這篇文章主要為大家詳細介紹了RabbitMQ中消息轉(zhuǎn)換器的相關(guān)知識,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以了解一下
    2023-04-04
  • Java實現(xiàn)Json字符串與Object對象相互轉(zhuǎn)換的方式總結(jié)

    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聊天程序——初始設計(附demo)

    詳解基于java的Socket聊天程序——初始設計(附demo)

    本篇文章主要介紹了Socket聊天程序——初始設計(附demo),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-12-12
  • RocketMQ線程池創(chuàng)建實現(xiàn)原理詳解

    RocketMQ線程池創(chuàng)建實現(xiàn)原理詳解

    這篇文章主要為大家介紹了RocketMQ線程池創(chuàng)建實現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • Spring常用數(shù)據(jù)源的xml配置詳解

    Spring常用數(shù)據(jù)源的xml配置詳解

    這篇文章主要介紹了Spring常用數(shù)據(jù)源的xml配置詳解,數(shù)據(jù)源是連接到數(shù)據(jù)庫的一類路徑,它包含了訪問數(shù)據(jù)庫的信息(地址、用戶名、密碼),數(shù)據(jù)源就像是排水管道,需要的朋友可以參考下
    2023-07-07
  • mybatis源碼解讀-Java中executor包的語句處理功能

    mybatis源碼解讀-Java中executor包的語句處理功能

    這篇文章主要介紹了Java中executor包的語句處理功能,在mybatis映射文件中傳參數(shù),主要用到#{}或者${},下文圍繞相關(guān)資料展開詳細內(nèi)容,需要的小伙伴可以參考一下
    2022-02-02
  • 關(guān)于在Java中使用預定義類

    關(guān)于在Java中使用預定義類

    這篇文章主要介紹了關(guān)于在Java中使用預定義類,預定義類就是Java類庫(或第三方庫)中已經(jīng)定義好的類,例如,Math 類和 Date 類,需要的朋友可以參考下
    2023-05-05
  • Java獲取當?shù)氐娜粘鋈章鋾r間代碼分享

    Java獲取當?shù)氐娜粘鋈章鋾r間代碼分享

    這篇文章主要介紹了Java獲取當?shù)氐娜粘鋈章鋾r間代碼分享,國外猿友寫的一個類,需要的朋友可以參考下
    2014-06-06
  • java客戶端線上Apollo服務端的實現(xiàn)

    java客戶端線上Apollo服務端的實現(xiàn)

    這篇文章主要介紹了java客戶端線上Apollo服務端的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-08-08
  • J2EE驗證碼圖片如何生成和點擊刷新驗證碼

    J2EE驗證碼圖片如何生成和點擊刷新驗證碼

    這篇文章主要介紹了J2EE如何生成驗證碼圖片如何生成,如何點擊刷新驗證碼的相關(guān)方法,感興趣的小伙伴們可以參考一下
    2016-04-04

最新評論