Java 類的 equals 方法(學習 Java 編程語言 048)

Object 類中的 equals 方法用于檢測一個對象是否等于另外一個對象垫桂。Object 類中實現(xiàn)的 equals 方法將確定兩個對象引用是否相等杭抠。這是一個合理的默認行為:如果兩個對象引用相等骂维,這兩個對象肯定就相等稀轨。 對于很多類來說填物,這就足夠了冰单。例如幌缝,比較兩個 PrintStream 對象是否相等并沒有多大的意義。不過诫欠,經常需要基于狀態(tài)監(jiān)測對象的相等性涵卵,如果兩個對象有相同的狀態(tài),才認為這兩個對象是相等的荒叼。

例如轿偎,如果兩個員工對象的姓名、薪水和雇用日期都一樣被廓,就認為它們是相等的坏晦。

class Employee
{
    private String name;
    private double salary;
    private LocalDate hireDay;

    ...

    @Override
    public boolean equals(Object otherObject)
    {
        // a quick test to see if the objects are identical
        if (this == otherObject) return true;

        // must return false if the explicit parameter is null
        if (otherObject == null) return false;

        // if the classes don't match, they can't be equal
        if (getClass() != otherObject.getClass())
            return false;
        
        // now we know otherObject is a non-null Employee
        Employee other = (Employee) otherObject;

        // test whether the fields have identical values
        return name.equals(other.name) 
                && salary == other.salary 
                && hireDay.equals(other.hireDay);
    }
}

public class Manager extends Employee
{
    ...

    @Override
    public boolean equals(Object otherObject)
    {
        if (!super.equals(otherObject)) return false;
        // super.equals checked that this and otherObject belong to the same class
        Manager other = (Manager) otherObject;
        return bonus == other.bonus;
    }
}

為了防備 name 或 hireDay 可能為 null 的情況,需要使用 Objects.equals 方法嫁乘。如果兩個參數(shù)都為 null英遭,Objects.equals(a, b) 調用將返回 true ; 如果其中一個參數(shù)為 null,則返回 false亦渗;否則,如果兩個參數(shù)都不為 null汁尺,則調用 a.equals(b)法精。 利用這個方法,Employee.equals 方法的最后一條語句要改寫為:

return Objects.equals(name, other.name)
    && salary == other.salary
    && Objects.equals(hireDay, other.hireDay);

在子類中定義 equals 方法時,首先調用超類的 equals搂蜓。如果檢測失敗狼荞,對象就不可能相等。如果超類中的域都相等帮碰,就需要比較子類中的實例域相味。

public class Manager extends Employee
{
    ...
    public boolean equals(Object otherObject)
    {
        if (!super.equals(otherObject)) return false;
        // super.equals checked that this and otherObject belong to the same class
        Manager other = (Manager) otherObject;
        return bonus == other.bonus;
    }
}

1. 相等測試與繼承

如果隱式和顯式的參數(shù)不屬于同一個類,equals 方法將如何處理呢殉挽?這是一個很有爭議的問題丰涉。在前面的例子中,如果發(fā)現(xiàn)類不匹配斯碌,equals 方法就返冋 false一死。但是,許多程序員卻喜歡使用 instanceof 進行檢測:
?if (!(otherObject instanceof Employee)) return false;
這樣就允許 otherObject 屬于一個子類傻唾,但是這種方法可能招致一些麻煩投慈。正式因為這些麻煩,所以建議不要采用這種處理方式冠骄。

Java 語言規(guī)范要求 equals 方法具有以下特性:

  1. 自反性:對于任何非空引用 x伪煤,x.equals(x) 應該返回 true。
  2. 對稱性:對于任何引用 x 和 y凛辣,當且僅當 y.equals(x) 返回 true 時抱既,x.equals(y) 返回 true。
  3. 傳遞性: 對于任何引用 x蟀给、 y 和 z蝙砌,如果 x.equals(y) 返回 true,y.equals(z) 返回 true跋理,x.equals(z) 也應該返回 true择克。
  4. 一致性:如果 x 和 y 引用的對象沒有發(fā)生變化,反復調用 x.equals(y) 應該返回同樣的結果前普。
  5. 對于任意非空引用 x肚邢,x.equals(null)應該返回 false。

這些規(guī)則當然合理拭卿。你肯定不希望類庫實現(xiàn)者在查找數(shù)據(jù)結構中的一個元素時還要糾結調用 x.equals(y) 還是調用 y.equals(x) 的問題骡湖。

不過,就對稱性規(guī)則來說峻厚,當參數(shù)不屬于同一個類的時候會有一個微妙的結果响蕴。請看下面的調用:
?e.equals(m);
Manager 是 Employee 的子類,e 是一個 Employee 對象惠桃,m 是一個 Manager 對象浦夷,并且兩個對象有相同的姓名辖试、薪水和雇傭日期。如果在 Employee.equals 中用 instanceof 進行檢測劈狐,則返回 true罐孝,然而這意味著反過來調用:
?m.equals(e);
也需要返回 true,對稱性不允許這個方法調用返回 false 或者拋出異常肥缔。
這就使得 Manager 類受到了束縛莲兢。這個類的 equals 方法必須愿意將自己與任何一個 Employee 對象進行比較,而不考慮 Manager 類特有的那部分信息续膳!猛然間會讓人感覺 instanceof 測試并不是那么好改艇。

有些作者認為 getClass 檢測是有問題的,因為它違反了替換原則姑宽。一個經常提到的例子遣耍,就是 AbstractSet 類的 equals 方法,它將檢測兩個集合是否有相同元素炮车。AbstractSet 類有兩個具體子類:TreeSet 和 HashSet舵变,它們分別使用不同的算法查找集合元素。但無論集合采用何種方式實現(xiàn)瘦穆,你肯定希望能夠比較任意的兩個集合纪隙。

不過,集合是非常特殊的一個例子扛或,應該將 AbstractSet.equals 聲明為 final绵咱,這是因為沒有任何一個子類需要重新頂定義集合相等性的語義(事實上,這個方法并沒有被聲明為 final熙兔。這樣做是為了讓子類實現(xiàn)更高效的算法來完成相等性檢測)悲伶。

就現(xiàn)在來看,有兩種完全不同的情形:

  • 如果子類可以有自己的相等性概念住涉,則對稱性需求將強制使用 getClass 檢測麸锉。
  • 如果由超類決定相等性概念,那么就可以使用 instanceof 檢測舆声,這樣可以在不同子類的對象之間進行相等性比較花沉。

在上面的 Employee 類和 Manager 類例子中,只要對應的字段相等媳握,就認為兩個對象相等碱屁。如果兩個 Manager 對象的姓名、薪水和雇用日期均相等蛾找,而獎金不相等娩脾,就認為它們是不相同的,因此打毛,我們要使用 getClass 檢測晦雨。
但是架曹,假設使用員工 ID 作為相等性檢測標準,并且這個相等性概念使用于所有的子類闹瞧,就可以使用 instanceof 檢測,而且應該將 Employee.equals 聲明為 final展辞。

注釋: 在標準 Java 庫中包含 150 多個 equals 方法的實現(xiàn)奥邮,包括使用 instanceof 檢測、調用 getClass 檢測罗珍、捕獲 ClassCastException 或者什么也不做等各種不同做法洽腺。可以查看 java.sql.Timestamp 類的 API 文檔覆旱,在這里實現(xiàn)人員不無尷尬地指出蘸朋,他們讓自己陷入了困境。Timestamp 類繼承自 java.util.Date扣唱,而后者的 equals 方法使用了一個 instanceof 測試藕坯,這樣一來就無法覆蓋 equals,使之同時做到對稱且正確噪沙。

下面給出編寫一個完美的 equals 方法的建議:

  1. 顯式參數(shù)命名為 otherObject, 稍后需要將它轉換成另一個名為 other 的變量炼彪。
  2. 檢測 this 與 otherObject 是否引用同一個對象:
    ?if (this == otherObject) return true;
    這條語句只是一個優(yōu)化。實際上正歼,這是一種經常采用的形式辐马。因為檢查身份要比逐個比較字段開銷小。
  3. 檢測 otherObject 是否為 null, 如果為 null, 返回 false局义。這項檢測是很必要的喜爷。
    ?if (otherObject == null) return false;
  4. 比較 this 與 otherObject 的類。如果 equals 的語義可以在子類中改變萄唇,就使用 getClass 檢測:
    ?if (getClass() != otherObject.getClass()) return false;
    如果所有的子類都有相同的相等性語義檩帐,可以使用 instanceof 檢測:
    ?if (!(otherObject instanceof ClassName)) return false;
  5. 將 otherObject 強制轉換為相應的類類型變量:
    ?ClassName other = (ClassName) otherObject
  6. 現(xiàn)在根據(jù)相等性概念的要求來比較字段。使用 == 比較基本類型字段穷绵,使用 Objects.equals 比較對象字段轿塔。如果所有的字段都匹配,就返回 true仲墨;否則返回 false勾缭。
    return field1 == other.field1
        && Objects.equals(field2, other.field2)
        && ...;
    
    如果子類中重新定義 equals,就要在其中包含一個 super.equals(other) 調用目养。

提示: 對于數(shù)組類型的字段俩由,可以使用靜態(tài)的 Arrays.equals 方法檢測相應的數(shù)組元素是否相等。

警告: 下面是實現(xiàn) equals 方法時的一種常見的錯誤癌蚁。

public class Employee
{
    public boolean equals(Employee other){
        return other != null
            && getClass() == other.getClass()
            && Objects.equals(name, other.name)
            && salary == other.salary
            && Objects.equals(hireDay, other.hireDay);
    }
    ...
}

這個方法聲明的顯式參數(shù)類型是 Employee幻梯。因此兜畸,它沒有覆蓋 Object 類的 equals 方法,而是定義了一個完全無關的方法碘梢。
為了避免發(fā)生這種錯誤咬摇,可以使用 @Override 標記要覆蓋超類方法的那些子類方法:
?@Override public boolean equals(Object other)
如果出現(xiàn)了錯誤,并且正在定義一個新方法煞躬,編譯器就會報告一個錯誤肛鹏。例如,假設將下面的聲明添加到 Employee 類中:
?@Override public boolean equals(Employee other)
就會看到一個錯誤報告恩沛,因為這個方法并沒有覆蓋超類 Object 中的任何方法在扰。

java.util.Arrays 1.2

  • static boolean equals(xxx[] a, xxx[] b) 5

    如果兩個組長度相同,并且在對應的位置上數(shù)據(jù)元素也相同雷客,將返回 true芒珠。數(shù)組的元素類型 xxx 可以是 Object、int搅裙、long皱卓、short、char呈宇、byte好爬、boolean、float 或 double甥啄。

java.util.Objects 7

  • static boolean equals(Object a, Object b)

    如果 a 和 b 都為null存炮,返回 true;如果只有其中之一為 null蜈漓,則返回 false穆桂;否則返回 a.equals(b)。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末融虽,一起剝皮案震驚了整個濱河市享完,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌有额,老刑警劉巖般又,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巍佑,居然都是意外死亡茴迁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門萤衰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堕义,“玉大人,你說我怎么就攤上這事脆栋【肼簦” “怎么了洒擦?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長怕膛。 經常有香客問我熟嫩,道長,這世上最難降的妖魔是什么嘉竟? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任邦危,我火速辦了婚禮,結果婚禮上舍扰,老公的妹妹穿的比我還像新娘。我一直安慰自己希坚,他們只是感情好边苹,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著裁僧,像睡著了一般个束。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上聊疲,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天茬底,我揣著相機與錄音,去河邊找鬼获洲。 笑死阱表,一個胖子當著我的面吹牛,可吹牛的內容都是我干的贡珊。 我是一名探鬼主播最爬,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼门岔!你這毒婦竟也來了爱致?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤寒随,失蹤者是張志新(化名)和其女友劉穎糠悯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妻往,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡互艾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒲讯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片忘朝。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖判帮,靈堂內的尸體忽然破棺而出局嘁,到底是詐尸還是另有隱情溉箕,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布悦昵,位于F島的核電站肴茄,受9級特大地震影響,放射性物質發(fā)生泄漏但指。R本人自食惡果不足惜寡痰,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棋凳。 院中可真熱鬧拦坠,春花似錦、人聲如沸剩岳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拍棕。三九已至晓铆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绰播,已是汗流浹背骄噪。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蠢箩,地道東北人链蕊。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像忙芒,于是被迫代替她去往敵國和親示弓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容