詳解hashCode()和equals()的本質(zhì)區(qū)別和聯(lián)系
在學(xué)習(xí)java,根據(jù)視頻做實(shí)例的過(guò)程中,對(duì)equals和hashcode兩個(gè)方法理解稍微深刻一點(diǎn),主要是它們兩個(gè)很容易混淆,容易出錯(cuò),自己又通過(guò)網(wǎng)上的資料學(xué)習(xí),和大家分享
equals()方法
equals是Object類(lèi)提供的方法之一,眾所周知,每一個(gè)java類(lèi)都繼承自O(shè)bject類(lèi),所以說(shuō)每一個(gè)對(duì)象都有equals這個(gè)方法。而我們?cè)谟眠@個(gè)方法時(shí)卻一般都重寫(xiě)這個(gè)方法,why?
先看一個(gè)Object類(lèi)中equals()方法的源代碼:
public boolean equals(Object obj) { return (this == obj); }
從這個(gè)方法中可以看出,只有當(dāng)一個(gè)實(shí)例等于它本身的時(shí)候,equals()才會(huì)返回true值。通俗地說(shuō),此時(shí)比較的是兩個(gè)引用是否指向內(nèi)存中的同一個(gè)對(duì)象,也可以稱做是否實(shí)例相等。而我們?cè)谑褂胑quals()來(lái)比較兩個(gè)指向值對(duì)象的引用的時(shí)候,往往希望知道它們邏輯上是否相等,而不是它們是否指向同一個(gè)對(duì)象——這就是我們通常重寫(xiě)這個(gè)方法的原因。
Strings1 = new String(“kvill”),String s2 = new String(“kvill”);s1.equals(s2)為ture,
說(shuō)明String類(lèi)中已經(jīng)重寫(xiě)了equals()方法,如果不重寫(xiě)equals()方法,那么s1.equals(s2)默認(rèn)比較兩個(gè)對(duì)象所指向的內(nèi)存地址是否相同,返回值必然為false。
當(dāng)然接下來(lái)我們要改寫(xiě)equals()方法,必須要遵守通用約定。來(lái)自java.lang.Object的規(guī)范,equals方法實(shí)現(xiàn)了等價(jià)關(guān)系,以下是要求遵循的5點(diǎn):
1.自反性:對(duì)于任意的引用值x,x.equals(x)一定為true。
2.對(duì)稱性:對(duì)于任意的引用值x 和 y,當(dāng)x.equals(y)返回true時(shí),y.equals(x)也一定返回true。
3.傳遞性:對(duì)于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。
4. 一致性:對(duì)于任意的引用值x 和y,如果用于equals比較的對(duì)象信息沒(méi)有被修改,多次調(diào)用x.equals(y)要么一致地返回true,要么一致地返回false。
5.非空性:對(duì)于任意的非空引用值x,x.equals(null)一定返回false。
hashCode()方法
hashcode()這個(gè)方法也是從object類(lèi)中繼承過(guò)來(lái)的,在object類(lèi)中定義如下:
public native int hashCode();
hashCode()返回該對(duì)象的哈希碼值,該值通常是一個(gè)由該對(duì)象的內(nèi)部地址轉(zhuǎn)換而來(lái)的整數(shù),它的實(shí)現(xiàn)主要是為了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。
必須銘記:在每個(gè)重寫(xiě)了equals方法的類(lèi)中,你必須也要重寫(xiě)hashCode方法。如果不這樣做的話,就會(huì)違反Object.hashCode的通用約定,從而導(dǎo)致該類(lèi)無(wú)法與所有基于散列值(hash)的集合類(lèi)結(jié)合在一起正常運(yùn)行。
hashCode()的返回值和equals()的關(guān)系如下:
如果x.equals(y)返回“true”,那么x和y的hashCode()必須相等。
如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。
public class TestEquals { public static void main(String args[]) { Student s1 = new Student("張一", 6); Student s2 = new Student("張一", 6); if (s1.equals(s2)) { System.out.println("相同 s1的代碼:" + s1.hashCode() + " s2的代碼:" + s2.hashCode()); } else { System.out.println("不相同"); } } } class Student { private int age; private String name; public Student() { } public Student(String name, int age) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int hashCode() { return (this.name.hashCode() + this.age) * 31; } public boolean equals(Object obj) { boolean result = false; if (obj == null) { result = false; } if (this == obj) { result = true; } if (obj instanceof Student) { Student stu = (Student) obj; if (stu.getName().equals(this.name) && stu.getAge() == (this.age)) { result = true; } } else { result = false; } return result; } }
詳細(xì)分析
equals()是判讀兩個(gè)Set是否相等[前提是equals()在類(lèi)中被覆蓋]。==決定引用值是否指向同一對(duì)象。
1、當(dāng)向集合set中增加對(duì)象時(shí),首先計(jì)算要增加對(duì)象的hashCode碼,根據(jù)該值來(lái)得到一個(gè)位置來(lái)存放當(dāng)前的對(duì)象,當(dāng)在該位置沒(méi)有一個(gè)對(duì)象存在的話,那么集合set認(rèn)為該對(duì)象在集合中不存在,直接增加進(jìn)去。如果在該位置有一個(gè)對(duì)象的話,接著將準(zhǔn)備增加到集合中的對(duì)象與該位置上的對(duì)象進(jìn)行equals方法比較,如果該equals方法返回false,那么集合認(rèn)為集合中不存在該對(duì)象,再進(jìn)行一次散列,將該對(duì)象放到散列后計(jì)算出的新地址里,如果equals方法返回true,那么集合認(rèn)為集合中已經(jīng)存在該對(duì)象了,不會(huì)再將該對(duì)象增加到集合中了。
2、當(dāng)重寫(xiě)equals方法時(shí),必須要重寫(xiě)hashCode方法。在java的集合中,判斷兩個(gè)對(duì)象是否相等的規(guī)則是:
1),判斷兩個(gè)對(duì)象的hashCode是否相等
如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等,完畢 ; 如果相等,轉(zhuǎn)入2
2),判斷兩個(gè)對(duì)象用equals運(yùn)算是否相等
如果不相等,認(rèn)為兩個(gè)對(duì)象也不相等
如果相等,認(rèn)為兩個(gè)對(duì)象相等(equals()是判斷兩個(gè)對(duì)象是否相等的關(guān)鍵)
可見(jiàn)hashcode()相等時(shí),equals()方法也可能不等。
public static void main(String args[]){ String s1=new String("zhaoxudong"); //此語(yǔ)句創(chuàng)建了兩個(gè)對(duì)象,一個(gè)是字符串對(duì)象“zhaoxudong”(存放于棧中的字面量),另一個(gè)是new后在堆中產(chǎn)生的對(duì)象。詳細(xì)見(jiàn)下面的四.4 String s2=new String("zhaoxudong"); //上述兩條語(yǔ)句一共是產(chǎn)生了三個(gè)對(duì)象,因?yàn)闂V兄挥挟a(chǎn)生了一個(gè)對(duì)象。 System.out.println(s1==s2);//false System.out.println(s1.equals(s2));//true System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() ,指向同一內(nèi)存的引用 System.out.println(s2.hashCode()); //equals和hashCode方法只用于兩個(gè)對(duì)象的比較和容器中,與對(duì)象的創(chuàng)建沒(méi)有關(guān)系 Set hashset=new HashSet(); hashset.add(s1); hashset.add(s2); /*在添加s1,s2時(shí), hashset認(rèn)為s1和s2是相等的,所以讓s2覆蓋了s1;*/ Iterator it=hashset.iterator(); while(it.hasNext()){ System.out.println(it.next()); } //最后在while循環(huán)的時(shí)候只打印出了一個(gè)”zhaoxudong”。
這是因?yàn)镾tring類(lèi)已經(jīng)重寫(xiě)了equals()方法和hashcode()方法。
但是看下面的程序:
>public class HashSetTest { public static void main(String[] args) { HashSet hs=new HashSet(); hs.add(new Student(1,"zhangsan")); hs.add(new Student(2,"lisi")); hs.add(new Student(3,"wangwu")); hs.add(new Student(1,"zhangsan")); Iterator it=hs.iterator(); while(it.hasNext()){ System.out.println(it.next()); } } } class Student { int num; String name; Student(int num,String name) { this.num=num; this.name=name; } public String toString() { return num+":"+name; } }
輸出結(jié)果為:
1:zhangsan 1:zhangsan 3:wangwu 2:lisi
為什么hashset添加了相等的元素呢?
這是不是和hashset的原則違背了呢?回答:沒(méi)有因?yàn)樵诟鶕?jù)hashcode()對(duì)兩次建立newStudent(1,"zhangsan")對(duì)象進(jìn)行比較時(shí),生成的是不同的哈希碼值,所以hashset把他當(dāng)作不同的對(duì)象對(duì)待了,當(dāng)然此時(shí)的equals()方法返回的值也不等。那么為什么會(huì)生成不同的哈希碼值呢?原因就在于我們自己寫(xiě)的Student類(lèi)并沒(méi)有重新自己的hashcode()和equals()方法
所以在比較時(shí),是繼承的object類(lèi)中的hashcode()方法,它是一個(gè)本地方法,比較的是對(duì)象的地址(引用地址),使用new方法創(chuàng)建對(duì)象,兩次生成的當(dāng)然是不同的對(duì)象了,造成的結(jié)果就是兩個(gè)對(duì)象的hashcode()返回的值不一樣。那么怎么解決這個(gè)問(wèn)題呢?
原因是:在Student類(lèi)中重新hashcode()和equals()方法。
class Student{ int num; String name; Student(int num,String name){ this.num=num; this.name=name; } public int hashCode(){ //重寫(xiě)hashCode的方法 return num*name.hashCode(); } public boolean equals(Object o) { Student s=(Student)o; return num==s.num && name.equals(s.name); //&&的優(yōu)先級(jí)比==低,所以前面不必加括號(hào) } public String toString(){return num+":"+name; } }
根據(jù)重寫(xiě)的方法,即便兩次調(diào)用了new Student(1,"zhangsan"),我們?cè)讷@得對(duì)象的哈希碼時(shí),根據(jù)重寫(xiě)的方法hashcode(),獲得的哈希碼肯定是一樣的。所以運(yùn)行修改后的程序時(shí),我們會(huì)看到重復(fù)元素的問(wèn)題已經(jīng)消除。
總結(jié)
這塊知識(shí)比較容易出錯(cuò),理解一定要深刻,多多的實(shí)踐會(huì)對(duì)原理與定義理解的更加的深刻。有什么問(wèn)題可以隨時(shí)留言,小編會(huì)及時(shí)回復(fù)大家的。感謝大家對(duì)本站的支持。
- java中hashCode方法與equals方法的用法總結(jié)
- 重寫(xiě)hashCode()和equals()方法詳細(xì)介紹
- JAVA hashCode使用方法詳解
- 詳解Java中用于查找對(duì)象哈希碼值的hashCode()函數(shù)
- 為什么在重寫(xiě) equals方法的同時(shí)必須重寫(xiě) hashcode方法
- java 中HashCode重復(fù)的可能性
- why在重寫(xiě)equals時(shí)還必須重寫(xiě)hashcode方法分享
- javascript中實(shí)現(xiàn)兼容JAVA的hashCode算法代碼分享
- 重新實(shí)現(xiàn)hashCode()方法
相關(guān)文章
使用spring通過(guò)aop獲取方法參數(shù)和參數(shù)值
這篇文章主要介紹了使用spring通過(guò)aop獲取方法參數(shù)和參數(shù)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Spring @Bean注解的使用場(chǎng)景與案例實(shí)現(xiàn)
隨著SpringBoot的流行,我們現(xiàn)在更多采用基于注解式的配置從而替換掉了基于XML的配置,所以本篇文章我們主要探討基于注解的@Bean以及和其他注解的使用2023-03-03MyBatis Generator的簡(jiǎn)單使用方法示例
這篇文章主要給大家介紹了關(guān)于MyBatis Generator的簡(jiǎn)單使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02mybatis-plus update更新操作的三種方式(小結(jié))
本文主要介紹了mybatis-plus update更新操作的三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Jenkins Pipeline為Kubernetes應(yīng)用部署增加狀態(tài)檢測(cè)腳本優(yōu)化
這篇文章主要為大家介紹了Jenkins Pipeline為Kubernetes應(yīng)用部署增加狀態(tài)檢測(cè)腳本優(yōu)化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12說(shuō)說(shuō)@ModelAttribute在父類(lèi)和子類(lèi)中的執(zhí)行順序
這篇文章主要介紹了@ModelAttribute在父類(lèi)和子類(lèi)中的執(zhí)行順序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06kafka生產(chǎn)者發(fā)送消息流程深入分析講解
本文將介紹kafka的一條消息的發(fā)送流程,從消息的發(fā)送到服務(wù)端的存儲(chǔ)。上文說(shuō)到kafak分為客戶端與服務(wù)端,要發(fā)送消息就涉及到了網(wǎng)絡(luò)通訊,kafka采用TCP協(xié)議進(jìn)行客戶端與服務(wù)端的通訊協(xié)議2023-03-03Spring Boot 基于注解的 Redis 緩存使用詳解
本篇文章主要介紹了Spring Boot 基于注解的 Redis 緩存使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05