天天看點

Java中的==和equals差別

中軟國際電子政務部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(object obj)方法,它比較兩個對象是否相等。

       其它對象的equals方法僅當被比較的兩個引用指向的對象内容相同時,對象的equals()方法傳回true。

       總之,"=="和"!="比較的是位址.也可認為"=="和"!="比較的是對象句柄;而equals()比較的是對象内容.或者說,,"=="和"!="比較的是"棧"中的内容,而equals()比較的是"堆"中的内容.

       ②==操作符。專門用來比較兩個變量的值是否相等,也就是用于比較變量所對應的記憶體中所存儲的數值是否相同,要比較兩個基本類型的資料或兩個引用變量是否相當,隻能用==操作符。

      java的基本資料類型為(char,byte,short,int,long,float,double,boolean)。

      如果一個變量指向的資料是對象類型的,那麼,這時候涉及了兩塊記憶體,對象本身占用一塊記憶體(對記憶體),變量本身也占用一塊記憶體,例如object obj = new object()變量obj是一個記憶體,new object()是一個記憶體,此時,變量所對應的記憶體中存儲的資料就是對象占用的那塊記憶體的首位址。對于指向對象記憶體的變量,如果要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應的記憶體中的數值是否相等,這時候就需要用==操作符進行比較。

       ③構造器形成的差别。對于string和integer來說,由于他們特有的建立對象的方式。使用構造器和不使用構造器得到一個對象,==方法比較所産生的結果是不同的。 string a = “abc”; string b = "abc"; 此時a==b得到結果為true。string a = new string("abc"); string b = new string("abc");此時a==b得到的結果為false。integer a = 1; integer

b = 1;此時a==b的結果是true。integer a = new integer(1); integer b = new integer(1);此時a==b得到的結果為false。

       通過這一點其實我們也能夠更加容易了解==對記憶體的實際操作,實際執行的是近似于基本類型比較。

       string對象和字元串連接配接池:

       引号内包含文本是string類特有建立對象的方式.但是"=="傳回的結果是true,為什麼呢?因為在jvm内,存在字元串池,其中儲存着很多 string對象,并且可以被共享使用,是以它提高了效率.字元串池由string類維護,我們可以調用intern()方法來通路字元串池。當運用引号内包含文本建立對象時,所建立的對象是加入到字元串池裡面的.如果要建立下一個字元串對象,jvm首先會到字元串池中尋找,是否存在對應的字元串對象,如果存在,則傳回一個己存在對象的對象的引用給要建立的對象引用.如果不存在,才會建立一個新的對象,并傳回一個新對象的對象引用給要建立的對象引用.以上這段話了解起來可能比較拗口.用代碼了解就是str2和str1是兩個對象引用,并指向了同一個對象.是以'=='傳回的是true.

       隻有引号内包含文本建立對象才會将建立的對象放入到字元串池。string str = new string("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=new string("abc");  

緊接着這段代碼之後的往往是這個問題,那就是這行代碼究竟建立了幾個string對象呢?相信大家對這道題并不陌生,答案也是衆所周知的,2個。接下來我們就從這道題展開,一起回顧一下與建立string對象相關的一些java知識。

我們可以把上面這行代碼分成string str、=、"abc"和new string()四部分來看待。string str隻是定義了一個名為str的string類型的變量,是以它并沒有建立對象;=是對變量str進行初始化,将某個對象的引用(或者叫句柄)指派給它,顯然也沒有建立對象;現在隻剩下new string("abc")了。那麼,new string("abc")為什麼又能被看成"abc"和new string()呢?我們來看一下被我們調用了的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 {  

    public static void main(string[] args) {  

        integer p = 1;   

         integer q = 1;

        integer i = new integer(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)"); //實際結果 

             system.out.println("integer:p.equals(q)");

        if(i == j){  

             system.out.println("int:i == j");  

             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 = new string("abc");  

         string d = new string("abc");  

        if(a == b){  

             system.out.println("abc對象相等"); //實際結果

             system.out.println("abc對象不相等");  

        if(a.equals(b)){  

             system.out.println("ab相等"); //實際結果

             system.out.println("ab不相等");  

        if(c.equals(d)){  

             system.out.println("cd相等"); //實際結果

             system.out.println("cd不相等");  

        if(c == d){  

             system.out.println("cd對象相等");  

             system.out.println("cd對象不相等"); //實際結果

     }  

----------------------------------------------------------------------------------

深入探讨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) {

else if (!attribute.equals(other.attribute)) {

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?

可能是有些疑問,但不知道如何做(不知道為什麼沒有打電話給我......)那麼對于非final類,如何進行類型的quick check呢?

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中并沒有這樣做,是以我才說是不恰當的例子)在完成quick check以後,我們就要真正實作你認為的“相等”。對于如果實作對象相等,沒有太高的要求,比如你自己實作的“人”類,你可以認為隻要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進行完全實作:

class c = this.getclass();

filed[] fds = c.getdeclaredfields();

for(filed f:fds){

if(!f.get(this).equals(f.get(obj)))

為了說明的友善,上明的實作省略了異常,這樣的實作放在父類中,可以保證你的子類的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)

if(!(o instanceof phonenumber))

phonenumber pn = (phonenumber)o;

return pn.extension == extension && pn.exchange == exchange && pn.areacode == areacode;

注意這個類是final的,是以這個equals實作沒有什麼問題。我們來測試一下:

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要求傳回相等的值。