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 方法具有以下特性:
- 自反性:對于任何非空引用 x伪煤,x.equals(x) 應該返回 true。
- 對稱性:對于任何引用 x 和 y凛辣,當且僅當 y.equals(x) 返回 true 時抱既,x.equals(y) 返回 true。
- 傳遞性: 對于任何引用 x蟀给、 y 和 z蝙砌,如果 x.equals(y) 返回 true,y.equals(z) 返回 true跋理,x.equals(z) 也應該返回 true择克。
- 一致性:如果 x 和 y 引用的對象沒有發(fā)生變化,反復調用 x.equals(y) 應該返回同樣的結果前普。
- 對于任意非空引用 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 方法的建議:
- 顯式參數(shù)命名為 otherObject, 稍后需要將它轉換成另一個名為 other 的變量炼彪。
- 檢測 this 與 otherObject 是否引用同一個對象:
?if (this == otherObject) return true;
這條語句只是一個優(yōu)化。實際上正歼,這是一種經常采用的形式辐马。因為檢查身份要比逐個比較字段開銷小。 - 檢測 otherObject 是否為 null, 如果為 null, 返回 false局义。這項檢測是很必要的喜爷。
?if (otherObject == null) return false;
- 比較 this 與 otherObject 的類。如果 equals 的語義可以在子類中改變萄唇,就使用 getClass 檢測:
?if (getClass() != otherObject.getClass()) return false;
如果所有的子類都有相同的相等性語義檩帐,可以使用 instanceof 檢測:
?if (!(otherObject instanceof ClassName)) return false;
- 將 otherObject 強制轉換為相應的類類型變量:
?ClassName other = (ClassName) otherObject
- 現(xiàn)在根據(jù)相等性概念的要求來比較字段。使用 == 比較基本類型字段穷绵,使用 Objects.equals 比較對象字段轿塔。如果所有的字段都匹配,就返回 true仲墨;否則返回 false勾缭。
如果子類中重新定義 equals,就要在其中包含一個return field1 == other.field1 && Objects.equals(field2, other.field2) && ...;
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)。