== 和equals() 比較結(jié)果不同的原因
=======================
對于這個(gè)問題涩僻,可以幫助我們很好的理解Java對象的創(chuàng)建欣范,賦值以及== 和equals()的用法变泄。
我們通過如下實(shí)例來說明,先看一個(gè)簡單的代碼:
public class Practice1 {
public static void main(String[] args) {
String str1=new String("hello");
String str2=new String("hello");
String str3="hello";
String str4="hello"; //結(jié)果為true System.out.println("equals: "+str1.equals(str2));
//1.結(jié)果為true
System.out.println("equals: "+str1.equals(str3));
//2.結(jié)果為false
System.out.println("str==str1 ?"+(str1==str3));
//結(jié)果為true System.out.println("str2==str1 ?"+(str3==str4));
}}
在回答問題之前恼琼,我們需要再來明確某些內(nèi)容妨蛹,它們可以幫助我們更好的理解問題的答案。
前文敘述有點(diǎn)長晴竞,也可先看第三部分蛙卤,如果看完之后還不是很懂,可以回來從此再看噩死。
一颤难、先談創(chuàng)建對象的相關(guān)內(nèi)容
=============
雖然我們都知道Java是面向?qū)ο蟮木幊蹋⒎峭耆拿嫦驅(qū)ο笠盐1热缯fJava中的基本數(shù)據(jù)類型行嗤,用int,double等創(chuàng)建的變量都不是對象垛耳。一般我們都是通過new 關(guān)鍵字來創(chuàng)建對象栅屏,而基本數(shù)據(jù)類型創(chuàng)建的變量并不能用new 的方式獲取。雖然如此堂鲜,但Java對基本數(shù)據(jù)類型也有相應(yīng)的解決辦法——封裝與其相應(yīng)的類栈雳,即Integer對應(yīng)int,Double對應(yīng)double缔莲,它們皆是為了解決基本數(shù)據(jù)類型面向?qū)ο笥玫摹?/p>
明確這些之后哥纫,我們再來看類型是如何分配的,基本數(shù)據(jù)類型在棧中進(jìn)行分配痴奏,而對象類型在堆中進(jìn)行分配蛀骇。基本類型之間的賦值是創(chuàng)建新的拷貝读拆,而對象之間的賦值只是傳遞引用松靡。所有方法的參數(shù)(基本類型除外)傳遞的都是引用而非本身的值。
* * *
現(xiàn)在建椰,再來聊聊String s="hello";以及String s = new String("hello");
我在之前的一篇博客中簡單的提到過String s="hello";(變量&數(shù)據(jù)類型),它與new不同岛马,同時(shí)也是java中唯一不需要new就可以產(chǎn)生對象的途徑棉姐。這種形式的賦值稱為——直接量,它被放在一個(gè)叫作字符串拘留池(常量池)的地方啦逆;而new 創(chuàng)建的對象都放在堆上伞矩。String s="hello" 這種形式的字符串,會(huì)在JVM(Java虛擬機(jī))中發(fā)生字符串拘留夏志。
那什么是字符串拘留呢乃坤?我們通過一個(gè)例子來理解這種機(jī)制,當(dāng)我們聲明一個(gè)字符串String s="hello";時(shí),JVM會(huì)先從常量池中查找有沒有值為"hello"的對象湿诊。如果有狱杰,會(huì)把該對象賦給當(dāng)前引用,也就是說原來的引用和現(xiàn)在的引用指向同一個(gè)對象厅须;如果沒有仿畸,則在拘留池中創(chuàng)建一個(gè)值為"hello"的對象,如果下一次還有類似的語句朗和,例如String str="hello";時(shí)错沽,又會(huì)將str指向"hello"這個(gè)對象。以這種形式聲明字符串眶拉,無論有多少個(gè)都指向同一個(gè)對象千埃。
再來說說String s = new String("hello");
這種形式創(chuàng)建的對象就和其他new 創(chuàng)建的對象一樣了,每執(zhí)行一次就生成一個(gè)新對象忆植,也就是說String str = new String("hello");與s毫無關(guān)系放可,他們是兩個(gè)獨(dú)立的對象,只不過巧了唱逢,他們的值或是說內(nèi)容相等吴侦。
* * *
我們也可以簡單的理解為:
String str = "hello"; 先在內(nèi)存中找是否有"hello"這個(gè)對象,如果有就可以直接從常量池中拿來用坞古,不用再從內(nèi)存中創(chuàng)建空間來存儲(chǔ)备韧。如果沒有,就創(chuàng)建一塊新內(nèi)存存著痪枫,以后要是有對象要用就直接給它用了织堂。
String str=new String ("hello") 就是不管內(nèi)存里有沒有"hello"這個(gè)對象,都新建一個(gè)對象保存"hello"奶陈。
看幾個(gè)例子:
String s1 = "qibao"; // 放在常量池中易阳,沒找到,新建一個(gè) String s2 = "qibao"; // 從常量池中查找吃粒,找到了潦俺,直接引用。s1徐勃,s2指向同一個(gè)對象
String s3 = new String("qibao"); // s3 為一個(gè)引用 String s4 = new String("qibao"); // s4 也是一個(gè)引用事示。雖然s3,s4對象的內(nèi)容一樣僻肖,但他們卻占著兩塊地肖爵。
String s5 = "qi" + "bao"; //字符串常量相加,在編譯時(shí)就會(huì)計(jì)算結(jié)果臀脏,s1 == s5 返回ture
String s6 = "qi"; String s7 = "bao"
String s8 = s6 + s7; //字符串變量相加劝堪,編譯時(shí)無法計(jì)算冀自,s1 == s8 返回false
class Person{
String name;
Person(String name) { this.name = name;}
}
Person p1 = new Person("qibao");
Person p2 = new Person("qibao");
p1.name == p2.name //返回true
* * *
二、再說== 跟equals() 的事
===================
* ? ==
先解釋幾個(gè)名詞:
* ? 寄存器:最快的存儲(chǔ)區(qū), 系統(tǒng)分配,程序中無法控制.
* ? 棧:基本數(shù)據(jù)類型變量和對象引用的存儲(chǔ)區(qū)秒啦,對象本身不放在棧中熬粗,而是存放在堆或者常量池中。
* ? 堆:new創(chuàng)建對象的存儲(chǔ)區(qū)帝蒿。
* ? 靜態(tài)域:靜態(tài)成員變量的存儲(chǔ)區(qū)荐糜。
* ? 常量池:基本數(shù)據(jù)類型常量和字符串常量的存儲(chǔ)區(qū)。
== 或 != 比較的是 棧中存放的對象引用 在堆上的地址葛超, 即判斷兩對象的堆地址是否相同暴氏,即是否是指相同一個(gè)堆對象。 對于基本類型绣张,== 和 != 是比較值答渔。 對于對象來說,== 和 != 是比較兩個(gè)引用,即判斷兩個(gè)對象的地址是否相同.
* ? equals()
我們先來看Object中定義的equals()
public boolean equals(Object obj) {
return (this == obj);
}
Object.equals()使用的算法區(qū)分度高侥涵,只要兩對象不是同一個(gè)就是錯(cuò)誤的沼撕。由于所有的類都繼承自O(shè)bject類,所以equals()適用于所有對象芜飘。Object中的equals方法返回 == 的判斷务豺,即對象的地址判斷。 雖然如此嗦明,但可以對Object.equals()進(jìn)行覆蓋笼沥,String類則實(shí)現(xiàn)覆蓋。我們再來看看String.equals():
private final char value[];
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}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;
}
查看String對equals覆蓋的源碼會(huì)發(fā)現(xiàn)娶牌,String.equals()相等的條件是:比較二者同為String類型奔浅,長度相等,且字符串值完全相同诗良,包括順序和值汹桦,不再要求兩者為同一對象。也可以理解為String.equals()將原本的String對象拆分成單個(gè)字符之間值的比較鉴裹,每個(gè)字符的比較完之后返回一個(gè)最終的boolean類型的值舞骆,即將原本可能指向不同堆地址的兩個(gè)對象 "間接的" 指向了同一個(gè)地址,以到達(dá)比較值的目的。
三径荔、解決問題的時(shí)候到了
===========
看完上邊這些內(nèi)容之后督禽,我們再來看這兩行代碼:
String str1=new String("hello");String str3="hello";//1.結(jié)果為trueSystem.out.println("equals: "+str1.equals(str3));//2.結(jié)果為falseSystem.out.println("str==str1 ?"+(str1==str3));
現(xiàn)在,我們就可以很清楚的知道為什么1 返回true:
String.equal() 只看兩者是否為String猖凛,長度是否相等,以及每個(gè)字符的值是否相等绪穆,很顯然str1和str2滿足這三點(diǎn)要求辨泳,所以返回結(jié)果為真虱岂。
至于2 返回false,我們也明白為何了:
== 是對對象地址的判斷菠红,而這兩種聲明方式的存儲(chǔ)形式是不同的第岖,因此它們的地址不同,自然返回false了试溯。
* * *
重載equals()方法
============
我們在寫程序時(shí)蔑滓,往往有時(shí)Java類庫中的equals方法不能滿足我們的需求。這時(shí)遇绞,就需要我們自己來定義equals方法键袱。
在寫自定義equals方法之前,我們先來看兩個(gè)類庫中已經(jīng)寫好的equals方法摹闽。
一蹄咖、Object.equals()
=================
很簡單的一個(gè)方法,因?yàn)槭荗bject的方法付鹿,所以對所有對象都適用澜汤。
public boolean equals(Object obj) {return (this == obj);}
二、String.equals()
=================
private final char value[];
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
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;
}
我們來看String類重寫equals方法時(shí)舵匾,都做了些什么俊抵。
首先是判斷是不是自己,如果是自己好辦坐梯,直接返回true就完事了徽诲。
然后如果不是自己,先看看傳入的參數(shù)是否為String類型烛缔,不是返回false就完事馏段。
再然后都是String類型了,在比較長度是否相等践瓷,每個(gè)字符的值是否相等院喜,全都相等就返回true。
三晕翠、自定義equals()
=============
通過模仿String.equals()喷舀,我們來寫Person.equals()。
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
} public void setAge(int age) {
this.age = age; }
public boolean equals(Object another) {
//先判斷是不是自己,提高運(yùn)行效率
if (this == another)
return true;
//再判斷是不是Person類,提高代碼的健壯性
if (another instanceof Person) {
//向下轉(zhuǎn)型,父類無法調(diào)用子類的成員和方法
Person anotherPerson = (Person) another;
//最后判斷類的所有屬性是否相等淋肾,其中String類型和Object類型可以用相應(yīng)的equals()來判斷
if ((this.getName().equals(anotherPerson.getName())) && (this.getAge() == anotherPerson.getAge()))
return true;
} else {
return false;
}
return false;
}}}
在覆蓋equals()時(shí)硫麻,我們在自定義equals內(nèi)部調(diào)用了Object.equals()和String.equals()。
四樊卓、自動(dòng)生成的equals()
===============
考慮到實(shí)際中我們可能會(huì)經(jīng)常覆寫equals()拿愧,因此eclipse為我們提供自動(dòng)生成的equals()。
操作過程如上圖所示碌尔,讀者可自行操作查看自動(dòng)生成的代碼浇辜。
個(gè)人公眾號:Java架構(gòu)師聯(lián)盟券敌,每日更新技術(shù)好文