1 問(wèn)題的提出
2 equals()和hashCode()區(qū)別
3 為什么選擇hashcode方法
4 為什么要重寫(xiě)equals方法
5 改寫(xiě)equals時(shí)總是要改寫(xiě)hashcode
6 hashCode方法使用介紹
1 問(wèn)題的提出
首先equals和==最大的區(qū)別是一個(gè)是方法一個(gè)是運(yùn)算符窥妇,在Java中,二者比較的都是物理地址 而不是值得比較烹骨。
舉個(gè)例子
Student student1 = new Student();
Student student2 = new Student();
System.out.println(student1.equals(student2));
System.out.println(student1 == student2);
不論是用的是哪一種方法 沮焕, 最終的結(jié)果顯示的都是false拉宗,大家不妨可以試一下。為什么呢急灭?就是因?yàn)樗麄儽容^的不是對(duì)象中字段的值或者說(shuō)本身對(duì)象的值葬馋,而比較的是物理地址点楼。
再來(lái)舉一個(gè)例子
String a = new String("a");
String b = new String("a");
System.out.println(a == b);
System.out.println(a.equals(b));
當(dāng)我們創(chuàng)建2個(gè)String對(duì)象是 我們會(huì)發(fā)現(xiàn) 執(zhí)行的結(jié)果是 false true白对。為什么這次euqals返回的值編程了true蟀瞧?因?yàn)榇藭r(shí)equals方法不單單是比較物理地址 同時(shí)也比較了值悦污。在String中 equals方法被重寫(xiě) 當(dāng)物理地址不同時(shí)钉蒲,會(huì)進(jìn)一步比較值顷啼。
那么問(wèn)題來(lái)了 當(dāng)我調(diào)用
System.out.println(student1.toString().equals(student2.toString()));
結(jié)果卻返回了false.為什么呢?這就牽扯到了hashcode的問(wèn)題。
那么為了保證兩個(gè)對(duì)象比較值相等有什么辦法么躬厌?想必大家都試過(guò)重寫(xiě)equals方法扛施,而最終的結(jié)果都不如人意疙渣。為什么昌阿?因?yàn)閱螁沃貙?xiě)equals方法并不能改變hashcode值懦冰,在java中 首先比較的就是hashcode。
2 equals()笋颤、hashCode()區(qū)別 和 equals()伴澄、==之間的區(qū)別
equals():反映的是對(duì)象或變量具體的值非凌。注意:對(duì)象里面包含的值:可能是對(duì)象的引用類型的值敞嗡,也可能是值類型的值航背。
hashCode():計(jì)算出對(duì)象實(shí)例的哈希碼玖媚,并返回哈希碼今魔,又稱為散列函數(shù)。根類Object的hashCode()方法的計(jì)算依賴于對(duì)象實(shí)例的內(nèi)存地址咏删,故每個(gè)Object對(duì)象的hashCode都是唯一的督函。
== 比較的是變量(棧)內(nèi)存中存放的對(duì)象的(堆)內(nèi)存地址辰狡,用來(lái)判斷兩個(gè)對(duì)象的地址是否相同垄分,即是否是指相同一個(gè)對(duì)象薄湿。比較的是真正意義上的指針操作吆倦。Object中的equals方法返回的是==的判斷。
String s="abce"是一種非常特殊的形式,和new 有本質(zhì)的區(qū)別晌梨。它是java中唯一不需要new 就可以產(chǎn)生對(duì)象的途徑须妻。String s="abce"JVM會(huì)在常量池中先查找有有沒(méi)有一個(gè)值為"abcd"的對(duì)象,如果有,就會(huì)把它賦給當(dāng)前引用.即原來(lái)那個(gè)引用和現(xiàn)在這個(gè)引用指點(diǎn)向了同一對(duì)象,如果沒(méi)有,則在常量池中新創(chuàng)建一個(gè)"abcd",下一次如果有String s1 = "abcd";又會(huì)將s1指向"abcd"這個(gè)對(duì)象,即以這形式聲明的字符串,只要值相等,任何多個(gè)引用都指向同一對(duì)象.
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 為一個(gè)引用
String b = new String("ab"); // b為另一個(gè)引用,對(duì)象的內(nèi)容一樣
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 從常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false敛惊,非同一對(duì)象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
(1)為什么需要hashcode方法豆混?
因?yàn)樵谂康膶?duì)象比較中皿伺,hashCode要比equals速度更快效率更高盒粮,所以很多集合都用到了hashCode妒穴,比如HashTable摊崭。
對(duì)象的比較的原則如下:
兩個(gè)obj呢簸,如果equals()相等根时,hashCode()一定相等。
兩個(gè)obj,如果hashCode()相等替裆,equals()不一定相等(Hash散列值有沖突的情況,雖然概率很低)惠赫。
所以在集合中,判斷兩個(gè)對(duì)象是否相等的規(guī)則是:
- 如果hashCode()相等倍阐,則查看equals()是否相等峰搪,相等則為同一對(duì)象凯旭。
- 查看equals()是否相等罐呼,如果相等,則兩obj相等。
(2)二者的聯(lián)系和區(qū)別
1 首先equals()和hashcode()這兩個(gè)方法都是從object類中繼承過(guò)來(lái)的
equals()是對(duì)兩個(gè)對(duì)象的地址值進(jìn)行的比較(即比較引用是否相同)夯尽。
hashCode()是一個(gè)本地方法匙握,它的實(shí)現(xiàn)是根據(jù)本地機(jī)器相關(guān)的陈轿。
2 Java要求equals()必須遵循:(1)對(duì)稱性麦射;(2)反射性法褥;(3)類推性;(4)一致性揍愁;
3 equals()相等的兩個(gè)對(duì)象莽囤,hashcode()一定相等朽缎;
反過(guò)來(lái):hashcode()不等,一定能推出equals()也不等;
hashcode()相等最筒,equals()可能相等床蜘,也可能不等邢锯。
3 為什么選擇hashcode方法
以java.lang.Object來(lái)理解,JVM每new一個(gè)Object,它都會(huì)將這個(gè)Object丟到一個(gè)Hash哈希表中去,這樣的話,下次做Object的比較或者取這個(gè)對(duì)象的時(shí)候,它會(huì)根據(jù)對(duì)象的hashcode再?gòu)腍ash表中取這個(gè)對(duì)象丹擎。這樣做的目的是提高取對(duì)象的效率鸥鹉。
具體過(guò)程如下:
new Object(),JVM根據(jù)這個(gè)對(duì)象的Hashcode值,放入到對(duì)應(yīng)的Hash表對(duì)應(yīng)的Key上,如果不同的對(duì)象確產(chǎn)生了相同的hash值,也就是發(fā)生了Hash key相同導(dǎo)致沖突的情況府适,那么就在這個(gè)Hash key的地方產(chǎn)生一個(gè)鏈表,將所有產(chǎn)生相同hashcode的對(duì)象放到這個(gè)單鏈表上去檐春,串在一起么伯。
比較兩個(gè)對(duì)象的時(shí)候,首先根據(jù)他們的hashcode去hash表中找他的對(duì)象,當(dāng)兩個(gè)對(duì)象的hashcode相同,那么就是說(shuō)他們這兩個(gè)對(duì)象放在Hash表中的同一個(gè)key上,那么他們一定在這個(gè)key上的鏈表上。那么此時(shí)就只能根據(jù)Object的equal方法來(lái)比較這個(gè)對(duì)象是否equal骨望。當(dāng)兩個(gè)對(duì)象的hashcode不同的話擎鸠,肯定他們不能equal缘圈。
舉個(gè)例子:list是可以重復(fù)的糟把,set是不可以重復(fù)的垂寥。那么set存儲(chǔ)數(shù)據(jù)的時(shí)候是怎樣判斷存進(jìn)的數(shù)據(jù)是否已經(jīng)存在另锋。使用equals()方法呢夭坪,還是hashcode()方法室梅。
假如用equals()疚宇,那么存儲(chǔ)一個(gè)元素就要跟已存在的所有元素比較一遍敷待,比如已存入100個(gè)元素,那么存101個(gè)元素的時(shí)候,就要調(diào)用equals方法100次思劳。
但如果用hashcode()方法的話潜叛,他就利用了hash算法來(lái)存儲(chǔ)數(shù)據(jù)的。
這樣的話每存一個(gè)數(shù)據(jù)就調(diào)用一次hashcode()方法,得到一個(gè)hashcode值及存入的位置牡属。如果該位置不存在數(shù)據(jù)那么就直接存入逮栅,否則調(diào)用一次equals()方法措伐,不相同則存侥加,相同不存担败。這樣下來(lái)整個(gè)存儲(chǔ)下來(lái)不需要調(diào)用幾次equals方法,雖然多了幾次hashcode方法,但相對(duì)于前面來(lái)講效率高了不少狈网。
4 為什么要重寫(xiě)equals方法
因?yàn)镺bject的equal方法默認(rèn)是兩個(gè)對(duì)象的引用的比較勇垛,意思就是指向同一內(nèi)存,地址則相等士鸥,否則不相等础淤;如果你現(xiàn)在需要利用對(duì)象里面的值來(lái)判斷是否相等,則重載equal方法。
說(shuō)道這個(gè)地方我相信很多人會(huì)有疑問(wèn)玻侥,相信大家都被String對(duì)象的equals()方法和"=="糾結(jié)過(guò)一段時(shí)間掌桩,當(dāng)時(shí)我們知道String對(duì)象中equals方法是判斷值的,而==是地址判斷茅坛。
那照這么說(shuō)equals怎么會(huì)是地址的比較呢则拷?
那是因?yàn)閷?shí)際上JDK中斥铺,String晾蜘、Math等封裝類都對(duì)Object中的equals()方法進(jìn)行了重寫(xiě)眠屎。
我們先看看Object中equals方法的源碼:
public boolean equals(Object obj) {
return (this == obj);
}
我們都知道所有的對(duì)象都擁有標(biāo)識(shí)(內(nèi)存地址)和狀態(tài)(數(shù)據(jù))组力,同時(shí)“==”比較兩個(gè)對(duì)象的的內(nèi)存地址燎字,所以說(shuō)使用Object的equals()方法是比較兩個(gè)對(duì)象的內(nèi)存地址是否相等候衍,即若object1.equals(object2)為true蛉鹿,則表示equals1和equals2實(shí)際上是引用同一個(gè)對(duì)象往湿。雖然有時(shí)候Object的equals()方法可以滿足我們一些基本的要求领追,但是我們必須要清楚我們很大部分時(shí)間都是進(jìn)行兩個(gè)對(duì)象的比較棕孙,這個(gè)時(shí)候Object的equals()方法就不可以了,所以才會(huì)有String這些類對(duì)equals方法的改寫(xiě)蟀俊,依次類推Double矛洞、Integer缚甩、Math等等窑邦。這些類都是重寫(xiě)了equals()方法的冈钦,從而進(jìn)行的是內(nèi)容的比較瞧筛。希望大家不要搞混了较幌。
5 改寫(xiě)equals時(shí)總是要改寫(xiě)hashcode
java.lnag.Object中對(duì)hashCode的約定:
- 在一個(gè)應(yīng)用程序執(zhí)行期間乍炉,如果一個(gè)對(duì)象的equals方法做比較所用到的信息沒(méi)有被修改的話,則對(duì)該對(duì)象調(diào)用hashCode方法多次,它必須始終如一地返回同一個(gè)整數(shù)槐瑞。
- 如果兩個(gè)對(duì)象根據(jù)equals(Object o)方法是相等的祠挫,則調(diào)用這兩個(gè)對(duì)象中任一對(duì)象的hashCode方法必須產(chǎn)生相同的整數(shù)結(jié)果悼沿。
- 如果兩個(gè)對(duì)象根據(jù)equals(Object o)方法是不相等的显沈,則調(diào)用這兩個(gè)對(duì)象中任一個(gè)對(duì)象的hashCode方法,不要求產(chǎn)生不同的整數(shù)結(jié)果鳖藕。但如果能不同著恩,則可能提高散列表的性能蜻展。
根據(jù)上一個(gè)問(wèn)題伍茄,實(shí)際上我們已經(jīng)能很簡(jiǎn)單的解釋這一點(diǎn)了施逾,比如改寫(xiě)String中的equals為基于內(nèi)容上的比較而不是內(nèi)存地址的話汉额,那么雖然equals相等蠕搜,但并不代表內(nèi)存地址相等,由hashcode方法的定義可知內(nèi)存地址不同,沒(méi)改寫(xiě)的hashcode值也可能不同俱萍。所以違背了第二條約定告丢。
又如new一個(gè)對(duì)象岖免,再new一個(gè)內(nèi)容相等的對(duì)象颅湘,調(diào)用equals方法返回的true栗精,但他們的hashcode值不同鹿寨,將兩個(gè)對(duì)象存入HashSet中脚草,會(huì)使得其中包含兩個(gè)相等的對(duì)象,因?yàn)槭窍葯z索hashcode值埂淮,不等的情況下才會(huì)去比較equals方法的倔撞。
6 hashCode方法使用介紹
Hash表數(shù)據(jù)結(jié)構(gòu)常識(shí):
1误窖、哈希表基于數(shù)組秩贰。
2丙唧、缺點(diǎn):基于數(shù)組的想际,數(shù)組創(chuàng)建后難以擴(kuò)展溪厘。某些哈希表被基本填滿時(shí)畸悬,性能下降得非常嚴(yán)重蹋宦。
3冷冗、沒(méi)有一種簡(jiǎn)便得方法可以以任何一種順序遍歷表中數(shù)據(jù)項(xiàng)蒿辙。
4滨巴、如果不需要有序遍歷數(shù)據(jù),并且可以提前預(yù)測(cè)數(shù)據(jù)量的大小绪颖,那么哈希表在速度和易用性方面是無(wú)與倫比的窃款。
參考鏈接
https://blog.csdn.net/pangjiuzala/article/details/48194549
https://www.cnblogs.com/jesonjason/p/5492208.html
https://www.cnblogs.com/Eason-S/p/5524837.html
https://blog.csdn.net/tcytcy123/article/details/50836323