Java中的"=="和"equals()"

前言

equals() 和 hashCode() 都是 Object 對(duì)象中的非 final 方法泌霍,它們?cè)O(shè)計(jì)的目的就是被用來覆蓋(override)的,所以在程序設(shè)計(jì)中還是經(jīng)常需要處理這兩個(gè)方法的漆改。而掌握這兩個(gè)方法的覆蓋準(zhǔn)則以及它們的區(qū)別還是很必要的,相關(guān)問題也不少准谚。

首先看看==和 equals() 的不同籽懦。

對(duì)于基本數(shù)據(jù)類型==比較的是它們的值氛魁。

Example

        int num1 = 1, num2 = 1;
        char ch1 = 'a', ch2 = 'a';
        if (num1 == num2 && ch1 == ch2) {
            System.out.println(num1 == num2);
            System.out.println(ch1 == ch2);
        } else {
            System.out.println(num1 == num2);
            System.out.println(ch1 == ch2);
        }

輸出結(jié)果:

true
true

對(duì)于引用類型==比較的是它們的內(nèi)存地址厅篓。以 String 為例:

        String str1 = "Hello World";
        String str2 = "Hello World";
        
        if (str1 == str2) {
            System.out.println("str1和str2的地址相同");
        } else {
            System.out.println("str1和str2的地址不同");
        }

輸出結(jié)果:

str1和str2的地址相同

根據(jù)String的源碼解釋:

The String class represents character strings. All string literals in Java programs, such as "abc", are implemented as instances of this class.Strings are constant; their values cannot be changed after they are created.

可見秀存,直接這樣初始化的字符串屬于 String 類的實(shí)現(xiàn),并且 String 類型的字符串自被創(chuàng)建后就是不可變的了羽氮。并且 Java 也推薦這樣初始化 String 或链,這是為什么呢?

在這一構(gòu)造函數(shù)的注釋中這樣寫道:

 public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

Initializes a newly created String object so that it represents the same sequence of characters as the argument; in other words, the newly created string is a copy of the argument string. Unless an explicit copy of original is needed, use of this constructor is unnecessary since Strings are immutable.

大意就是說這樣初始化 String 對(duì)象档押,其字符序列是與參數(shù)相同的澳盐;也就是說新創(chuàng)建的字符串是參數(shù)字符串的副本祈纯。 因此除非需要顯式的原始副本,否則不必使用此構(gòu)造函數(shù)叼耙,因?yàn)樽址遣豢勺兊摹?/p>

那為什么字符串是不可變的呢腕窥?

在 String 源碼中可以看到,其實(shí)字符串是被存放到了一個(gè) char 類型的數(shù)組中筛婉,且該數(shù)組被 final 關(guān)鍵字修飾簇爆,因此創(chuàng)建好的字符串是不可變的也就可以想通了。

/** The value is used for character storage. */
    private final char value[];

因此下面的語(yǔ)句是等價(jià)的:

String str = "abc";
//str等價(jià)于:
char [] data = {'a', 'b', 'c'};
String str = new String(data);

這樣理解了一番之后爽撒,就可以得出一些結(jié)論:

  • 如果要初始化字符串入蛆,直接給 String 類型的變量賦值即可;
  • 但如果要轉(zhuǎn)換數(shù)組為字符串硕勿, String 類提供了不少相應(yīng)的構(gòu)造函數(shù)哨毁,可以查閱文檔選擇合適的使用。
  • 用 String 類創(chuàng)建的字符串是不可變的(constant)源武。

回到最開始的問題扼褪,又有一個(gè)新的問題:String是怎么判斷兩個(gè)字符串引用地址的呢?

在String的源碼中提到了字符串緩沖池(A pool of strings):

A pool of strings, initially empty, is maintained privately by the class String.
...
if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

這個(gè)緩沖池是由 String 類維護(hù)的软能,當(dāng)緩沖池中已經(jīng)存在一個(gè)和傳入字符串相同的字符串(通過調(diào)用 equals() 方法來確定是否相同)迎捺,那么就返回緩沖池中的字符串。否則查排,就把傳入的新字符串添加到緩沖池中并返回這個(gè)字符串的引用凳枝。

結(jié)合之前提到的使用構(gòu)造函數(shù)來創(chuàng)建新字符串的方式,這種方式新創(chuàng)建的字符串是傳入字符串的副本跋核,現(xiàn)在對(duì)這句話進(jìn)行解釋岖瑰。看下面的例子:

        String str1 = "Hello";
        String str2 = new String("Hello");

        if (str1 == str2) {
            System.out.println("str1和str2的地址相同");
        } else {
            System.out.println("str1和str2的地址不同");
        }

輸出結(jié)果為:

str1 和 str2 的地址不同

  • str1 創(chuàng)建的字符串被加入到字符串緩沖池中去砂代, str1 指向緩沖池中的 "Hello" 蹋订;
  • str2 調(diào)用構(gòu)造函數(shù)傳入 "Hello" ,實(shí)際上進(jìn)行了兩步:
  1. 先去緩沖池中找有沒有相同的字符串(通過調(diào)用 equals() 方法來確定是否相同)刻伊;
  2. 發(fā)現(xiàn)緩沖池中有相同的字符串露戒,那就不需要新創(chuàng)建一遍了;而如果沒有捶箱,就新創(chuàng)建一個(gè)字符串智什。這里的情況顯然是第一種。但是由于 str2 中調(diào)用了構(gòu)造函數(shù)丁屎,因此要在內(nèi)存的堆空間上新建一個(gè)對(duì)象荠锭,而這個(gè) str2 正是指向內(nèi)存堆上的一個(gè)地址空間。

因此 str1 和 str2 通過 == 判斷到的地址空間是不同的晨川。


所有類中的 equals() 方法都是繼承自或重寫 Object 類中 equals() 方法的证九。Object類提供的 equals() 方法如下:

 public boolean equals(Object obj) {
       return (this == obj);
   }

可見最原始的 equals() 方法其實(shí)就是調(diào)用的==删豺。對(duì)于任何非空(non-null)的引用變量 x 和 y ,當(dāng)且僅當(dāng) x 和 y 指向同一個(gè)對(duì)象的時(shí)候愧怜,equals() 方法就返回 true 呀页。

重寫 equals() 的準(zhǔn)則,這個(gè)在 Object 類中有提到過:

  • 自反性:
    It is reflexive: x.equals(x) should return true;
  • 對(duì)稱性:
    It is symmetric: x.equals(y) should return true if and only if y.equals(x) returns true;
  • 傳遞性:
    It is transitive: if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • 一致性:
    It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • 非空性:
    x.equals(null) should return false.

那么問題來了叫搁,哪些情況下會(huì)違反對(duì)稱性和傳遞性赔桌?

  • 違反對(duì)稱性

對(duì)稱性就是x.equals(y)時(shí),y也得equals x渴逻,很多時(shí)候疾党,我們自己覆寫equals時(shí),讓自己的類可以兼容等于一個(gè)已知類惨奕,比如下面的例子:

public final class CaseInsensitiveString {
    private final String s;
    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o instanceof CaseInsensiticeString)
            return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
        if (o instanceof String)
            return s.equalsIgnoreCase((String) o);
        return false;
    }
}

這個(gè)想法很好雪位,想創(chuàng)建一個(gè)無(wú)視大小寫的String,并且還能夠兼容String作為參數(shù)梨撞,假設(shè)我們創(chuàng)建一個(gè)CaseInsensitiveString:

CaseInsensitiveString cis = new CaseInsensitiveString("Case");

那么肯定有 cis.equals("case"),問題來了雹洗,"case".equals(cis)嗎? String 并沒有兼容 CaseInsensiticeString 卧波,所以 String 的 equals() 也不接受 CaseInsensiticeString 作為參數(shù)时肿。

所以有個(gè)準(zhǔn)則,一般在覆寫 equals() 只兼容同類型的變量港粱。

  • 違反傳遞性

傳遞性就是A等于B螃成,B等于C,那么A也應(yīng)該等于C查坪。

假設(shè)我們定義一個(gè)類Cat寸宏。

public class Cat(){
    private int height;
    private int weight;
    public Cat(int h, int w)
    {
        this.height = h;
        this.weight = w;
    }
    
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Cat))
            return false;
        Cat c = (Cat) o;
        return c.height == height && c.weight == weight; 
    }
}

名人有言,不管黑貓白貓抓住老鼠就是好貓偿曙,我們又定義一個(gè)類ColorCat:

public class ColorCat extends Cat{
    private String color;
    public ColorCat(int h, int w, String color)
    {
        super(h, w);
        this.color = color;
    }

我們?cè)趯?shí)現(xiàn) equals 方法時(shí)氮凝,可以加上顏色比較,但是加上顏色就不兼容和普通貓作對(duì)比了望忆,這里我們忘記上面要求只兼容同類型變量的建議罩阵,定義一個(gè)兼容普通貓的 equals 方法,在“混合比較”時(shí)忽略顏色启摄。

@Override
public boolean equals(Object o) {
    if (! (o instanceof Cat))
        return false; //不是Cat或者ColorCat永脓,直接false
    if (! (o instanceof ColorCat))
        return o.equals(this);//不是彩貓,那一定是普通貓鞋仍,忽略顏色對(duì)比
    return super.equals(o)&&((ColorCat)o).color.equals(color); //這時(shí)候才比較顏色
}

假設(shè)我們定義了貓:

ColorCat whiteCat = new ColorCat(1,2,"white");
Cat cat = new Cat(1,2);
ColorCat blackCat = new ColorCat(1,2,"black");

此時(shí)有whiteCat等于catcat等于blackCat搅吁,但是whiteCat不等于blackCat威创,所以不滿足傳遞性要求落午。


源碼注釋中還提到,無(wú)論何時(shí)當(dāng) equals() 方法被重寫的時(shí)候肚豺,都有必要去重寫一下 hashCode() 方法以便維持 hashCode() 方法的通用契約(general contract)溃斋,這個(gè)契約就是相同的對(duì)象必須具有相同的哈希值

類庫(kù)提供的 equals() 方法吸申,如果已經(jīng)重寫的話梗劫,那么比較的也許就不止是地址空間了,這就看具體類庫(kù)是怎么實(shí)現(xiàn)的了截碴。以 String 類為例梳侨,它提供的 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;
    }
  • 首先會(huì)判斷當(dāng)前對(duì)象和傳入對(duì)象是否指向相同的地址空間,如果是就直接返回 true 日丹;
  • 如果指向的地址空間不同走哺,檢測(cè)傳入對(duì)象是否為 String 類的實(shí)例,如果是就比較兩個(gè)對(duì)象中值是否相同哲虾,如果相同丙躏,返回 true ;否則返回 false 束凑;
  • 如果傳入對(duì)象不是 String 類的實(shí)例晒旅,返回 false 。

Example

        String str1 = "Hello";
        String str2 = new String("Hello");
        if (str2.equals(str1)) {
            System.out.println("str1 equals to str2");
        } else {
            System.out.println("str1 doesn't equals to str2");
        }

輸出結(jié)果為:

str1 equals to str2

原因顯而易見汪诉。


參考:
面試官愛問的equals與hashCode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末废恋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摩瞎,更是在濱河造成了極大的恐慌拴签,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旗们,死亡現(xiàn)場(chǎng)離奇詭異蚓哩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)上渴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門岸梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人稠氮,你說我怎么就攤上這事曹阔。” “怎么了隔披?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵赃份,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)抓韩,這世上最難降的妖魔是什么纠永? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮谒拴,結(jié)果婚禮上尝江,老公的妹妹穿的比我還像新娘。我一直安慰自己英上,他們只是感情好炭序,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苍日,像睡著了一般惭聂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上易遣,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天彼妻,我揣著相機(jī)與錄音,去河邊找鬼豆茫。 笑死侨歉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的揩魂。 我是一名探鬼主播幽邓,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼火脉!你這毒婦竟也來了牵舵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤倦挂,失蹤者是張志新(化名)和其女友劉穎畸颅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體方援,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡没炒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犯戏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片送火。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖先匪,靈堂內(nèi)的尸體忽然破棺而出种吸,到底是詐尸還是另有隱情,我是刑警寧澤呀非,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布坚俗,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猖败。R本人自食惡果不足惜形耗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辙浑。 院中可真熱鬧,春花似錦拟糕、人聲如沸判呕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)侠草。三九已至,卻和暖如春犁嗅,著一層夾襖步出監(jiān)牢的瞬間边涕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工褂微, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留功蜓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓宠蚂,卻偏偏與公主長(zhǎng)得像式撼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子求厕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容