最近準(zhǔn)備再刷刷面試題忍抽,將面試題中比較經(jīng)典和核心的內(nèi)容寫成系列文章發(fā)表在公眾號中八孝,鞏固基礎(chǔ)知識,分享給大家鸠项,歡迎大家持續(xù)關(guān)注【程序新視界】干跛。下面是本系列第1篇。
大多數(shù)面試的第一題不是讓說說面向?qū)ο笏畎恚褪顷P(guān)于字符的楼入。本篇文章就從各方面來聊聊“==和equals的區(qū)別”哥捕。
概念上的區(qū)別
針對字符串(注意僅限字符串)的比較,==和equals的區(qū)別有以下兩點(diǎn):
(1)"=="是判斷兩個(gè)變量或?qū)嵗遣皇侵赶蛲粋€(gè)內(nèi)存空間嘉熊。
(2)"equals"是判斷兩個(gè)變量或?qū)嵗赶虻膬?nèi)存空間的值是不是相同遥赚。
單純從抽象的概念來看上面的描述還是比較晦澀難懂的。為了講解清楚上面的概念阐肤,我們先來簡單了解一下JVM內(nèi)存分配的知識凫佛。
創(chuàng)建對象的內(nèi)存分配
在JVM中,內(nèi)存分為堆內(nèi)存和棧內(nèi)存孕惜。通常情況愧薛,當(dāng)我們通過new關(guān)鍵字創(chuàng)建一個(gè)對象時(shí),就會調(diào)用對象的構(gòu)造函數(shù)來開辟空間衫画,將對象數(shù)據(jù)存儲到堆內(nèi)存中厚满,與此同時(shí)在棧內(nèi)存中生成對應(yīng)的引用。
String str = new String("程序新視界");
上述代碼中碧磅,真實(shí)的String對象存儲在堆內(nèi)存中碘箍,str變量僅持有指向該對象的引用地址。當(dāng)在后續(xù)代碼調(diào)用時(shí)鲸郊,用的都是棧內(nèi)存中的引用(str指向的地址)丰榴。
String是如何實(shí)現(xiàn)equals方法的
了解了上面的概念,我們再來看看String中是如何實(shí)現(xiàn)equals方法的秆撮。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
上面的代碼分兩部分四濒。第一部分,直接通過“==”進(jìn)行比較职辨,我們已經(jīng)知道這是比較對象的引用地址是否相等盗蟆。也就是說如果兩個(gè)對象的引用地址相同,那么它們便是相等的舒裤。
第二部分代碼喳资,判斷傳入的對象是否為String對象,如果是String對象并且兩個(gè)String對象的值的char[]數(shù)組中的每個(gè)元素值都相等腾供,則它們便是相等的仆邓。
看完了上述代碼,大家可能就明白了在講述它們的區(qū)別時(shí)為什么要添加上“注意僅限字符串”的備注了伴鳖。String的equals方法之所以比較的是值节值,是因?yàn)樗貙懥薳quals方法。
匯總一下榜聂,針對String的比較可以用下面一張圖來展示:
我們知道Java中所有的類都繼承自O(shè)bject對象搞疗,而Object對象中也定義了equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
我們看到了什么?Object的equals方法比較的竟然也是引用地址须肆!所以匿乃,如果單單的說“==”比較的是引用脐往,equals比較的是引用對應(yīng)的值,是錯(cuò)誤的扳埂!這里要限定于String類這個(gè)范圍业簿。
當(dāng)我們定義一個(gè)類時(shí),如果未重寫equals方法時(shí)便使用的是Object默認(rèn)的equals方法阳懂。如果重寫該方法梅尤,則按照重寫的方法實(shí)現(xiàn)進(jìn)行比較,String的equals方法便是重寫的示例之一岩调。
特殊的String定義
String除了通過new的形式進(jìn)行定義巷燥,還可以通過等號賦值的形式:
String str = "程序新視界";
這是一種非常特殊的形式,不需要new就可以產(chǎn)生對象号枕,和new有本質(zhì)的區(qū)別缰揪。這種形式的賦值在java中叫直接量,它是存在于常量池中葱淳,而不是像new一樣存放在堆中钝腺。
當(dāng)聲明這樣一個(gè)字符串時(shí),JVM會在常量池中先查找有沒有對應(yīng)值的對象赞厕。如果有艳狐,把它賦給當(dāng)前引用,即原來的引用和現(xiàn)在的引用指向了同一對象皿桑。如果沒有毫目,則在常量池中新創(chuàng)建一個(gè)對象。以這形式聲明的字符串诲侮,只要值相等镀虐,任何多個(gè)引用都指向同一對象。
對照new形式創(chuàng)建String對象和創(chuàng)建其他對象一樣沟绪,每次調(diào)用就產(chǎn)生一個(gè)新的對象刮便。
示例驗(yàn)證
下面以具體的實(shí)例來驗(yàn)證以上結(jié)論。同時(shí)近零,這些驗(yàn)證的實(shí)例也有可能是面試題的考點(diǎn)內(nèi)容诺核。
String x = "程序新視界";
String y = "程序新視界";
String z = new String("程序新視界");
System.out.println(x == y); // true
System.out.println(x == z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
第一行,因?yàn)槎际峭ㄟ^賦值創(chuàng)建對象久信,當(dāng)內(nèi)存中已經(jīng)存在x對應(yīng)的對象,賦值y對象時(shí)直接將引用指向原有對象漓摩。因此相等裙士。
第二行,因?yàn)閦通過new形式創(chuàng)建管毙,會創(chuàng)建新的對象腿椎,此處比較的是兩個(gè)對象的引用地址桌硫,因此不相等。
第三啃炸、四行铆隘,均比較字符串的實(shí)際值,因此相等南用。
下面再看一下未重寫equals方法的對象比較膀钠。對應(yīng)的實(shí)體類定義和單元測試方法如下:
@Test
public void testObject(){
Person p1 = new Person("Tom");
Person p2 = new Person("Tom");
System.out.println(p1.equals(p2));
}
class Person{
public Person(String name){
this.name = name;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
執(zhí)行上述方法,打印結(jié)果為false裹虫。
通過以上兩個(gè)實(shí)例肿嘲,均驗(yàn)證了我們上面所講的理論。
小結(jié)
經(jīng)過上面的分析筑公,理解了底層的邏輯雳窟,想必大家再遇到類似面試題時(shí)便能準(zhǔn)確回答。
簡單的說通過等號比較的是引用匣屡,通過equals比較的是值封救。從嚴(yán)格意義上來說是錯(cuò)誤的。通過JVM對象的存儲形式以及重寫equals方法等底層實(shí)現(xiàn)原理來進(jìn)行解答才能體現(xiàn)你的實(shí)力捣作,而不是死記硬背兴泥。
下篇文章,我們來講講new String的形式創(chuàng)建了幾個(gè)對象及底層邏輯虾宇,歡迎持續(xù)關(guān)注搓彻。
程序新視界:精彩和成長都不容錯(cuò)過!