饑人谷技術(shù)博客-Equal()和HashCode的相關(guān)理解

前言

這篇文章主要幫你理解equal()hashcode()這兩個方法,而后你可以將它們應(yīng)用到實際工程中;
在使用Java中collections時,添加collections中的某個元素的類的時候,應(yīng)該適當(dāng)覆蓋equal()hashcode(),否則會出錯;
Object類(java中所有類的父類)定義了兩個方法equals()hashCode(),這意味著java中所有的類(包括我們自己創(chuàng)建的類)都將繼承這些方法.
但是,對于將對象添加到collections中的類,必須重寫這兩方法,尤其是基于哈希表的集合,例如:HashSet和HashMap;

equals()方法的理解

當(dāng)比較兩個對象的時候,java就調(diào)用equals()方法,如果兩個對象相等,則返回true,否則返回false;(注意:equal()與==是有很大區(qū)別的)
區(qū)別在于:
equals()比較的是兩個對象的數(shù)據(jù)成員,而==比較的是兩個對象的引用(即內(nèi)存地址)
注意:object類中的equals()比較的是兩個對象的引用,我們自己在寫類的時候必須將這個方法覆蓋以進(jìn)行語義比較;
JDK中幾乎所有的類都有自己的equals()方法,比如String拜轨,Date,Integer,Double等;
看下面的一個例子:

String s1 = new String("This is a string");
String s2 = new String("This is a string");
 
boolean refEqual = (s1 == s2);
boolean secEqual = (s1.equals(s2));
 
System.out.println("s1 == s2: " + refEqual);
System.out.println("s1.equals(s2): " + secEqual);

你能猜到結(jié)果么,請看:

s1 == s2: false
s1.equals(s2): true

你可以看到,引用比較(==操作符)返回false;因為s1和s2是存儲在內(nèi)存中的不同地址;
而語義比較返回true,因為這兩個字符串的值相同(同一個字符串),故可以認(rèn)定其語義是相等 的;
再看一個例子:

public class Student {
    private String id;
    private String name;
    private String email;
    private int age;
 
    public Student(String id, String name, String email, int age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }
 
    public String toString() {
        String studentInfo = "Student " + id;
        studentInfo += ": " + name;
        studentInfo += " - " + email;
        studentInfo += " - " + age;
 
        return studentInfo;
    }
}

如果兩個Student對象具有相同的屬性(id既绕、姓名掉瞳、電子郵件和年齡),則可以認(rèn)為它們在語義上是相等的∷В現(xiàn)在秕豫,讓我們看看如何在這個類中重寫equals()方法來確認(rèn)兩個具有相同屬性的學(xué)生對象是相等的;


public boolean equals(Object obj) {
    if (obj instanceof Student) {
        Student another = (Student) obj;
        if (this.id.equals(another.id) &&
            this.name.equals(another.name) &&
            this.email.equals(another.email) &&
            this.age == another.age) {
                return true;
        }
    }
 
    return false;
}




在這里,此equals()方法檢查傳遞的對象是否為Student類型观蓄,以及它是否具有與當(dāng)前對象相同的屬性混移,相同則(返回true);否則,返回false侮穿。 讓我們用以下代碼進(jìn)行測試:

Student student1 = new Student("123", "Tom", "tom@gmail.com", 30);
Student student2 = new Student("123", "Tom", "tom@gmail.com", 30);
Student student3 = new Student("456", "Peter", "peter@gmail.com", 23);
 
System.out.println("student1 == student2: " + (student1 == student2));
System.out.println("student1.equals(student2): " + (student1.equals(student2)));
System.out.println("student2.equals(student3): " + (student2.equals(student3)));

測試結(jié)果:


student1 == student2: false
student1.equals(student2): true
student2.equals(student3): false

讓我們來看另一個示例,以理解重寫equals()方法的作用歌径。假設(shè)我們有一個這樣的學(xué)生列表

List<Student> listStudents = new ArrayList<>();

此列表包含上面的三個Student對象:


listStudents.add(student1);
listStudents.add(student2);
listStudents.add(student3);

現(xiàn)在,我們要檢查列表中是否包含具有給定ID的學(xué)生亲茅。 我會告訴您一個使用equals()方法的簡單有趣的解決方案回铛。

請注意,List接口提供了contains(Object)方法克锣,該方法可用于檢查列表中是否存在指定的對象茵肃。 在后臺,列表調(diào)用搜索對象上的equals()方法將其與集合中的其他對象進(jìn)行比較。

如果兩個Student對象有相同的ID袭祟,是否可以認(rèn)為它們是相等的?所以我們像這樣更新Student類中的equals()方法;


public boolean equals(Object obj) {
    if (obj instanceof Student) {
        Student another = (Student) obj;
        if (this.id.equals(another.id)) {
                return true;
        }
    }
 
    return false;
}



在這里,此equals()方法僅比較兩個Student對象的ID屬性验残。 并將另一個構(gòu)造函數(shù)添加至Student類:

public Student(String id) {

    this.id = id;

}



現(xiàn)在,我們可以執(zhí)行如下檢查:


Student searchStudent1 = new Student("123");
Student searchStudent4 = new Student("789");
 
boolean found1 = listStudents.contains(searchStudent1);
boolean found4 = listStudents.contains(searchStudent4);
 
System.out.println("Found student1: " + found1);
System.out.println("Found student4: " + found4);


結(jié)果如下:


Found student1: true
Found student4: false

多虧了equals()方法巾乳,使我們的代碼變得簡單.想象一下,如果不使用它您没,我們將實現(xiàn)更復(fù)雜的搜索功能,如下所示:

public boolean searchStudent(List<Student> listStudents, String id) {
    for (Student student : listStudents) {
        if (student.getId().equals(id)) {
            return true;
        }
    }
 
    return false;
}

HashCode的理解

Object類定義hashCode()方法胆绊,如下所示:

public int hashCode()

您可以看到這個方法返回一個整數(shù).那么它在哪里呢?
基于哈希表的集合(如Hashtable,HashSet和HashMap)使用此哈希數(shù)將對象存儲在稱為“存儲桶”的小容器中. 每個存儲桶都與一個哈希碼相關(guān)聯(lián)氨鹏,每個存儲桶僅包含具有相同哈希碼的對象。

換句話說压状,哈希表通過其哈希碼值將其元素分組仆抵。 這種安排通過搜索集合的一小部分而不是整個集合來幫助哈希表快速有效地定位元素。

以下是在哈希表中定位元素的步驟:

  • 獲取指定元素的哈希碼值何缓。 這里hashCode()方法將被調(diào)用肢础。

  • 找到與該哈希碼關(guān)聯(lián)的正確存儲桶。

  • 在存儲桶內(nèi)部,通過將指定元素與存儲桶中的所有元素進(jìn)行比較,找到正確的元素.這將導(dǎo)致調(diào)用指定元素的equals()方法碌廓。

當(dāng)我們將類的對象添加到基于哈希表的集合(HashSet,HashMap)時,該類的hashCode()方法將被調(diào)用以產(chǎn)生一個整數(shù)(可以是任意值),集合使用此數(shù)字來快速有效地存儲和定位對象,因為基于哈希表的集合不會保持其元素的順序传轰。

注意:Object類中hashCode()的默認(rèn)實現(xiàn)返回一個整數(shù),該整數(shù)是對象的內(nèi)存地址.我們應(yīng)該在自己的類中重寫它. JDK中的幾乎所有類都會覆蓋其自己的hashCode()方法版本,例如String,Date,Integer,Double等。

hashcode()和equals()之間的規(guī)則與約定

如上所述谷婆,基于哈希表的集合由于是通過調(diào)用元素的hashCode()和equals()方法來定位元素慨蛙,因此對于重寫這些方法的方式辽聊,我們必須遵守此約定:

  • 當(dāng)equals()方法被覆蓋時,hashCode()方法也必須被覆蓋期贫。

  • 如果兩個對象相等,則它們的哈希碼也必須相等跟匆。

  • 如果兩個對象不相等,則它們的哈希碼沒有限制(它們的哈希碼可以相等或不相等).

  • 如果兩個對象具有相同的哈希碼,則它們的相等性不受限制(它們可以相等或不相等).

  • 如果兩個對象具有不同的哈希碼,則它們不能相等。

通過遵循這些規(guī)則通砍,我們使集合在維護(hù)其元素方面保持一致玛臂。 如果違反這些規(guī)則,則集合將表現(xiàn)出異常情況封孙,例如找不到對象迹冤,或者返回錯誤的對象而不是正確的對象.

hashcode()和equal()聯(lián)合實戰(zhàn)

現(xiàn)在,讓我們回到上面那個student例子,看看hashCode()和equals()方法如何影響Set的行為的

public class Student {
    private String id;
    private String name;
    private String email;
    private int age;
 
    public Student(String id) {
        this.id = id;
    }
 
    public Student(String id, String name, String email, int age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }
 
    public String toString() {
        String studentInfo = "Student " + id;
        studentInfo += ": " + name;
        studentInfo += " - " + email;
        studentInfo += " - " + age;
 
        return studentInfo;
    }
 
    public boolean equals(Object obj) {
        if (obj instanceof Student) {
            Student another = (Student) obj;
            if (this.id.equals(another.id)) {
                    return true;
            }
        }
 
        return false;
    }
}


注意,到目前為止,只有equals()方法被覆蓋。

我們將三個Student對象添加到HashSet中,如以下代碼所示:


Student student1 = new Student("123", "Tom", "tom@gmail.com", 30);
Student student2 = new Student("123", "Tom", "tom@gmail.com", 30);
Student student3 = new Student("456", "Peter", "peter@gmail.com", 23);
 
Set<Student> setStudents = new HashSet<Student>();
 
setStudents.add(student1);
setStudents.add(student2);
setStudents.add(student3);


現(xiàn)在虎忌,讓我們使用Lambda表達(dá)式打印此集合中所有學(xué)生的信息:


setStudents.forEach(student -> System.out.println(student));

得到結(jié)果如下:


Student 456: Peter - peter@gmail.com - 23
Student 123: Tom - tom@gmail.com - 30
Student 123: Tom - tom@gmail.com - 30


不知您是否注意到似乎有2個重復(fù)的學(xué)生(ID:123)

我們希望該集合包含不重復(fù)的元素,這為什么是可能的呢?(上面明明重復(fù)了啊)

原因在這里:

該集合對要添加的每個對象調(diào)用equals()和hashCode()方法,以確保沒有重復(fù).在我們的例子中,Student類僅覆蓋equals()方法,

從Object類繼承的hashCode()方法返回每個對象的內(nèi)存地址,該地址與equals()方法不一致(這違反了上面的約定,如下圖所示,這兩個

對象所分配的地址就不一樣).因此,該集合將student1和student2對象視為兩個不同的元素泡徙。

現(xiàn)在,讓我們重寫Student類中的hashCode()方法膜蠢,以遵守equals()和hashCode()的約定. 需要添加以下代碼:

public int hashCode() {
    return 31 + id.hashCode();
}

此方法基于id屬性的哈希碼返回整數(shù)(其hashCode()方法被String類覆蓋).運行代碼以再次打印集并觀察結(jié)果:


Student 123: Tom - tom@gmail.com - 30
Student 456: Peter - peter@gmail.com - 23


image.png

調(diào)試驗證

image.png
image.png
image.png
image.png
image.png

此時student1和student2的hashcode是相等的,故這里會調(diào)用equals()函數(shù)來判斷兩個對象是是否相等

image.png

hashcode方法():通過查看源碼,我們這里可以知道:

其實hashcod的本質(zhì)就是通過某種算法將傳進(jìn)來的對象映射為一個隨機(jī)數(shù)(如果兩個對象的屬性是一樣的,其哈希碼肯定一樣!!!)


<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">public int hashCode() {
    int h = hash;
 if (h == 0 && value.length > 0) {
        char val[] = value;   for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
  }
        hash = h;
  }
    return h; }</pre>

image.png

在正確地覆蓋了equals()和hashCode()方法之后堪藐,我們還可以像這樣對集合執(zhí)行搜索:


Student searchStudent = new Student("456");

boolean found = setStudents.contains(searchStudent);

System.out.println("Found student: " + found);

結(jié)果:


Found student: true

總結(jié)

請記住以下幾點:

  • equals()方法比較兩個對象在語義上是否相等,例如 比較類的數(shù)據(jù)成員挑围。

  • hashCode()方法返回一個整數(shù)值礁竞,該整數(shù)值用于在基于哈希表的集合的存儲桶中分配元素氛琢。

并記住equals()和hashCode()方法之間的約定:

  • 當(dāng)equals()方法被覆蓋時,hashCode()方法也必須被覆蓋。

  • 如果兩個對象相等,則它們的哈希碼也必須相等杀迹。

  • 如果兩個對象不相等,則它們的哈希碼沒有限制(它們的哈希碼可以相等或不相等)刃泌。

  • 如果兩個對象具有相同的哈希碼,則它們的相等性不受限制(它們可以相等或不相等)。

  • 如果兩個對象具有不同的哈希碼,則它們不能相等洽议。

本文正在參加“寫編程博客瓜分千元現(xiàn)金”活動,關(guān)注公眾號“饑人谷”回復(fù)“編程博客”參與活動

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子硼端,更是在濱河造成了極大的恐慌,老刑警劉巖寓搬,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珍昨,死亡現(xiàn)場離奇詭異,居然都是意外死亡句喷,警方通過查閱死者的電腦和手機(jī)镣典,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唾琼,“玉大人兄春,你說我怎么就攤上這事∥荩” “怎么了赶舆?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵哑姚,是天一觀的道長。 經(jīng)常有香客問我芜茵,道長叙量,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任九串,我火速辦了婚禮绞佩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猪钮。我一直安慰自己品山,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布躬贡。 她就那樣靜靜地躺著谆奥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拂玻。 梳的紋絲不亂的頭發(fā)上酸些,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機(jī)與錄音檐蚜,去河邊找鬼魄懂。 笑死,一個胖子當(dāng)著我的面吹牛闯第,可吹牛的內(nèi)容都是我干的市栗。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼咳短,長吁一口氣:“原來是場噩夢啊……” “哼填帽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咙好,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤篡腌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后勾效,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘹悼,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年层宫,在試婚紗的時候發(fā)現(xiàn)自己被綠了杨伙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡萌腿,死狀恐怖限匣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哮奇,我是刑警寧澤膛腐,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布睛约,位于F島的核電站,受9級特大地震影響哲身,放射性物質(zhì)發(fā)生泄漏辩涝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一勘天、第九天 我趴在偏房一處隱蔽的房頂上張望怔揩。 院中可真熱鬧,春花似錦脯丝、人聲如沸商膊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晕拆。三九已至,卻和暖如春材蹬,著一層夾襖步出監(jiān)牢的瞬間实幕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工堤器, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留昆庇,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓闸溃,卻偏偏與公主長得像整吆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辉川,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348