Java中的==和equals區別
引言:從一個朋友的blog轉過來的,裏面解決了兩個困擾我很久的問題。很有久旱逢甘霖的感覺。
中軟國際電子政務部Jeff Chi總結,轉載請說明出處。
概述:
A.==可用於基本類型和引用類型:當用於基本類型時候,是比較值是否相同;當用於引用類型的時候,是比較對象是否相同。
B.對於String a = “a”; Integer b =1;這種類型的特有對象創建方式,==的時候值是相同的。
C.基本類型沒有equals方法,equals只比較值(對象中的內容)是否相同(相同返回true)。
D.一個類如果沒有定義equals方法,它將默認繼承Object中的equals方法,返回值與==方法相同。
詳述:
①==和equals的實質。
在JAVA中利用"=="比較變量時,系統使用變量在"棧"中所存的值作爲比較的依據。
基本數據類型在"棧"中存的是其內容值,而對象類型在"棧"中存的是地址,這些地址指向"堆"中的對象。
java.lang包中的Object類有public boolean equals(Objectobj)方法,它比較兩個對象是否相等。
其它對象的equals方法僅當被比較的兩個引用指向的對象內容相同時,對象的equals()方法返回true。
總之,"=="和"!="比較的是地址.也可認爲"=="和"!="比較的是對象句柄;而equals()比較的是對象內容.或者說,,"=="和"!="比較的是"棧"中的內容,而equals()比較的是"堆"中的內容.
②==操作符。專門用來比較兩個變量的值是否相等,也就是用於比較變量所對應的內存中所存儲的數值是否相同,要比較兩個基本類型的數據或兩個引用變量是否相當,只能用==操作符。
Java的基本數據類型爲(char,byte,short,int,long,float,double,boolean)。
如果一個變量指向的數據是對象類型的,那麼,這時候涉及了兩塊內存,對象本身佔用一塊內存(對內存),變量本身也佔用一塊內存,例如Objectobj = new Object()變量obj是一個內存,newObject()是一個內存,此時,變量所對應的內存中存儲的數據就是對象佔用的那塊內存的首地址。對於指向對象內存的變量,如果要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應的內存中的數值是否相等,這時候就需要用==操作符進行比較。
③構造器形成的差別。對於String和Integer來說,由於他們特有的創建對象的方式。使用構造器和不使用構造器得到一個對象,==方法比較所產生的結果是不同的。String a = “abc”; String b = "abc"; 此時a==b得到結果爲true。String a = newString("abc"); String b = newString("abc");此時a==b得到的結果爲false。Integer a = 1; Integer b =1;此時a==b的結果是true。Integer a = new Integer(1); Integer b = newInteger(1);此時a==b得到的結果爲false。
通過這一點其實我們也能夠更加容易理解==對內存的實際操作,實際執行的是近似於基本類型比較。
String對象和字符串連接池:
引號內包含文本是String類特有創建對象的方式.但是"=="返回的結果是true,爲什麼呢?因爲在JVM內,存在字符串池,其中保存着很多String對象,並且可以被共享使用,因此它提高了效率.字符串池由String類維護,我們可以調用intern()方法來訪問字符串池。當運用引號內包含文本創建對象時,所創建的對象是加入到字符串池裏面的.如果要創建下一個字符串對象,JVM首先會到字符串池中尋找,是否存在對應的字符串對象,如果存在,則返回一個己存在對象的對象的引用給要創建的對象引用.如果不存在,纔會創建一個新的對象,並返回一個新對象的對象引用給要創建的對象引用.以上這段話理解起來可能比較拗口.用代碼理解就是str2和str1是兩個對象引用,並指向了同一個對象.所以'=='返回的是true.
只有引號內包含文本創建對象纔會將創建的對象放入到字符串池。String str = newString("abc")這種方法創建的字符串對象是不放入到字符串池的。所以,引號內包含文本創建對象的性能要比後來那種方法創建字符串對象的性能要好。
String str1 = "abc";
String str2 = "abc";
String str3 = str1+str2; //這種創建方式是不放入字符串池的.
String str4 = str1+"cd"; //這種創建方式是不放入字符串池的.
String str5 = "ab"+str2; //這種創建方式是不放入字符串池的.
String str6 = "ab"+"cd";//這種創建方式是放入字符串池的.這種情況實際上是創建了1個對象,abcd"1個對象
String str7 = "abcd";
System.out.println(str1==str2); //返回ture
System.out.println(str6==str7); //返回ture
另一個問題:
我們首先來看一段 Java代碼:
String str=newString("abc");
緊接着這段代碼之後的往往是這個問題,那就是這行代碼究竟創建了幾個String對象呢?相信大家對這道題並不陌生,答案也是衆所周知的,2個。接下來我們就從這道題展開,一起回顧一下與創建String對象相關的一些JAVA知識。
我們可以把上面這行代碼分成String str、=、"abc"和new String()四部分來看待。Stringstr只是定義了一個名爲str的String類型的變量,因此它並沒有創建對象;=是對變量str進行初始化,將某個對象的引用(或者叫句柄)賦值給它,顯然也沒有創建對象;現在只剩下newString("abc")了。那麼,new String("abc")爲什麼又能被看成"abc"和newString()呢?我們來看一下被我們調用了的String的構造器:
Java代碼
public String(String original){
//other code...
}
大家都知道,我們常用的創建一個類的實例(對象)的方法有以下兩種:
我們正是使用new調用了String類的上面那個構造器方法創建了一個對象,並將它的引用賦值給了str變量。同時我們注意到,被調用的構造器方法接受的參數也是一個String對象,這個對象正是"abc"。
使用new創建對象是調用Class類的newInstance方法,利用反射機制創建對象。
④equals方法。用於比較兩個獨立對象的內容是否相同,就好比去比較兩個人的長相是否相同,它比較的兩個對象是獨立的。例如,對於下面的代碼:
String a=new String("foo");
String b=new String("foo");
兩條new語句創建了兩個對象,然後用a,b這兩個變量分別指向了其中一個對象,這是兩個不同的對象,他們的首地址是不同的,即a和b中存儲的數值是不相同的,所以,表達式a==b即返回false,而這兩個對象中內容是相同的,所以,表達式a.equals(b)將返回true。
在實際開發中,我們經常要比較傳遞進行來的字符串內容是否相等,許多人稍不注意就使用==進行比較了,這是錯誤的,有大量這樣的錯誤。記住,字符串的比較基本都是使用equals方法。
⑤如果一個類沒有定義equals方法。它將繼承Object類的equals方法,Object類的equals方法的實現代碼如下:
boolean equals(Object o){
return this==o;
}
這說明,如果一個類沒有自己定義equals方法,它默認的equals方法(從Object類繼承的)就是使用==操作符,也是比較兩個變量指向的對象是否是同一個對象,這時候使用equals和使用==會得到同樣的結果,如果比較的是兩個獨立的對象則總返回false。如果你編寫的類希望能夠比較該類創建的兩個實例對象的內容是否相同,那麼你必須覆蓋equals方法,由你自己寫代碼來決定在什麼情況即可以認爲兩個對象的內容是相同的。
示例代碼:
- public class Test{
- publicstatic void main(String[] args){
- Integer p =1;
- Integer q = 1;
- Integer i = newInteger(1);
- Integer j = new Integer(1);
- if(p== q){
- System.out.println("integer:p== q");//實際結果
- }else{
- System.out.println("integer:p!=q");
- }
- if(p.equals(q)){
- System.out.println("integer:p.equals(q)");//實際結果
- }else{
- System.out.println("integer:p.equals(q)");
- }
- if(i== j){
- System.out.println("int:i ==j");
- }else{
- System.out.println("int:i != j");//實際結果
- }
- if(i.equals(j)){
- System.out.println("integer:i.equals(j)");//實際結果
- }else{
- System.out.println("integer:!i.equals(j)");
- }
- String a ="abc";
- String b ="abc";
- String c = newString("abc");
- String d = newString("abc");
- if(a== b){
- System.out.println("abc對象相等");//實際結果
- }else{
- System.out.println("abc對象不相等");
- }
- if(a.equals(b)){
- System.out.println("ab相等");//實際結果
- }else{
- System.out.println("ab不相等");
- }
- if(c.equals(d)){
- System.out.println("cd相等");//實際結果
- }else{
- System.out.println("cd不相等");
- }
- if(c== d){
- System.out.println("cd對象相等");
- }else{
- System.out.println("cd對象不相等");//實際結果
- }
- }
- }
中軟國際電子政務部Jeff Chi總結,轉載請說明出處。
----------------------------------------------------------------------------------
深入探討equals:
===================
轉自硅谷動力
equals方法的重要性毋須多言,只要你想比較兩個對象是不是同一對象,你就應該實現equals方法,讓對象用你認爲相等的條件來進行比較.
下面的內容只是API的規範,沒有什麼太高深的意義,但我之所以最先把它列在這兒,是因爲這些規範在事實中並不是真正能保證得到實現.
1.對於任何引用類型, o.equals(o) == true成立.
2.如果 o.equals(o1) == true 成立,那麼o1.equals(o)==true也一定要成立.
3.如果 o.equals(o1) == true 成立且 o.equals(o2) == true 成立,那麼
o1.equals(o2) == true 也成立.
4.如果第一次調用o.equals(o1) == true成立,在o和o1沒有改變的情況下以後的任何次調用都成立.
5.o.equals(null) == true 任何時間都不成立.
以上幾條規則並不是最完整的表述,詳細的請參見API文檔.對於Object類,它提供了一個最最嚴密的實現,那就是隻有是同一對象時,equals方法才返回true,也就是人們常說的引用比較而不是值比較.這個實現嚴密得已經沒有什麼實際的意義,所以在具體子類(相對於Object來說)中,如果我們要進行對象的值比較,就必須實現自己的equals方法.先來看一下以下這段程序:
public boolean equals(Object obj)
{
if (obj == null) return false;
if (!(obj instanceof FieldPosition))
return false;
FieldPosition other = (FieldPosition) obj;
if (attribute == null) {
if (other.attribute != null) {
return false;
}
}
else if (!attribute.equals(other.attribute)) {
return false;
}
return (beginIndex == other.beginIndex
& endIndex == other.endIndex
&& field == other.field);
}
這是JDK中java.text.FieldPosition的標準實現,似乎沒有什麼可說的.我信相大多數或絕大多數程序員認爲,這是正確的合法的equals實現.畢竟它是JDK的API實現啊.還是讓我們以事實來說話吧:
package debug
;import java.text.*;
public class Test {
public static void main(String[] args) {
FieldPosition fp = new FieldPosition(10);
FieldPosition fp1 = new MyTest(10);
System.out.println(fp.equals(fp1));
System.out.println(fp1.equals(fp));
}
}
class MyTest extends FieldPosition{
int x = 10;
public MyTest(int x){
super(x);
this.x = x;
}
public boolean equals(Object o){
if(o==null) return false;
if(!(o instanceof MyTest )) return false;
return ((MyTest)o).x == this.x;
}
}
運行一下看看會打印出什麼:
System.out.println(fp.equals(fp1));打印true
System.out.println(fp1.equals(fp));打印flase
兩個對象,出現了不對稱的equals算法.問題出在哪裏(腦筋急轉彎:當然出在JDK實現的BUG)?我相信有太多的程序員(除了那些根本不知道實現equals方法的程序員外)在實現equals方法時都用過instanceof運行符來進行短路優化的,實事求是地說很長一段時間我也這麼用過。
太多的教程,文檔都給了我們這樣的誤導。而有些稍有了解的程序員可能知道這樣的優化可能有些不對但找不出問題的關鍵。另外一種極端是知道這個技術缺陷的骨灰級專家就提議不要這樣應用。我們知道,"通常"要對兩個對象進行比較,那麼它們"應該"是同一類型。所以首先利用instanceof運算符進行短路優化,如果被比較的對象不和當前對象是同一類型則不用比較返回false。
但事實上,"子類是父類的一個實例",所以如果子類 o instanceof父類,始終返回true,這時肯定不會發生短路優化,下面的比較有可能出現多種情況,一種是不能造型成父類而拋出異常,另一種是父類的private成員沒有被子類繼承而不能進行比較,還有就是形成上面這種不對稱比較。可能會出現太多的情況。
那麼,是不是就不能用instanceof運算符來進行優化?答案是否定的,JDK中仍然有很多實現是正確的,如果一個class是final的,明知它不可能有子類,爲什麼不用instanceof來優化呢?爲了維護SUN的開發小組的聲譽,我不說明哪個類中,但有一個小組成員在用這個方法優化時在後加加上了加上了這樣的註釋:
if (this == obj) // quick check
return true;
if (!(obj instanceof XXXXClass)) // (1) same object?
return false;
可能是有些疑問,但不知道如何做(不知道爲什麼沒有打電話給我......)那麼對於非final類,如何進行類型的quickcheck呢?
if(obj.getClass() != XXXClass.class) return false;
用被比較對象的class對象和當前對象的class比較,看起來是沒有問題,但是,如果這個類的子類沒有重新實現equals方法,那麼子類在比較的時候,obj.getClass()肯定不等於XXXCalss.class, 也就是子類的equals將無效,所以
if(obj.getClass() != this.getClass()) return false;
纔是正確的比較。另外一個quick check是if(this==obj) return true;
是否equals方法比較的兩個對象一定是要同一類型?上面我用了"通常",這也是絕大多數程序員的願望,但是有些特殊的情況,我們可以進行不同類型的比較,這並不違反規範。但這種特殊情況是非常罕見的,一個不恰當的例子是,Integer類的equals可以和Sort做比較,比較它們的value是不是同一數學值。(事實上JDK的API中並沒有這樣做,所以我才說是不恰當的例子)在完成quickcheck以後,我們就要真正實現你認爲的“相等”。對於如果實現對象相等,沒有太高的要求,比如你自己實現的“人”類,你可以認爲只要name相同即認爲它們是相等的,其它的sex,ago都可以
不考慮。這是不完全實現,但是如果是完全實現,即要求所有的屬性都是相同的,那麼如何實現equals方法?
class Human{
private String name;
private int ago;
private String sex;
....................
public boolean equals(Object obj){
quick check.......
Human other = (Human)ojb;
return this.name.equals(other.name)&& this.ago == ohter.ago&&this.sex.equals(other.sex);
}
}
這是一個完全實現,但是,有時equals實現是在父類中實現,而要求被子類繼承後equals能正確的工
作,這時你並不事實知道子類到底擴展了哪些屬性,所以用上面的方法無法使equals得到完全實現。
一個好的方法是利用反射來對equals進行完全實現:
public boolean equals(Object obj){
quick check.......
Class c = this.getClass();
Filed[] fds = c.getDeclaredFields();
for(Filed f:fds){
if(!f.get(this).equals(f.get(obj)))
return false;
}
return true;
}
爲了說明的方便,上明的實現省略了異常,這樣的實現放在父類中,可以保證你的子類的equals可以按你的願望正確地工作。關於equals方法的最後一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一定要重寫hashCode().這是規範,否則.............
我們還是看一下這個例子:
public final class PhoneNumber {
private final int areaCode;
private final int exchange;
private final int extension;
public PhoneNumber(int areaCode, int exchange, int extension){
rangeCheck(areaCode, 999, "area code");
rangeCheck(exchange, 99999999, "exchange");
rangeCheck(extension, 9999, "extension");
this.areaCode = areaCode;
this.exchange = exchange;
this.extension = extension;
}
private static void rangeCheck(int arg, int max, String name){
if(arg < 0 || arg > max)
throw new IllegalArgumentException(name + ": " + arg);
}
public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.extension == extension &&pn.exchange == exchange &&pn.areaCode == areaCode;
}
}
注意這個類是final的,所以這個equals實現沒有什麼問題。我們來測試一下:
public static void main(String[] args) {
Map hm = new HashMap();
PhoneNumber pn = new PhoneNumber(123, 38942, 230);
hm.put(pn, "I love you");
PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);
System.out.println(pn);
System.out.println("pn.equals(pn1) is " + pn.equals(pn1));
System.out.println(hm.get(pn1));
System.out.println(hm.get(pn));
}
既然pn.equals(pn1),那麼我put(pn,"I love you")後,get(pn1)爲什麼是null呢?
答案是因爲它們的hashCode不一樣,而hashMap就是以hashCode爲主鍵的。所以規範要求,如果兩個對象進行equals比較時如果返回true,那麼它們的hashcode要求返回相等的值。