Java Equals方法

Equals方法

    Object類中的equals方法用於檢測一個對象是否等於另一個對象。在Object類中,這個方法將判斷兩個對象是否具備相同的引用。若是兩個對象具備相同的引用,它們必定是相等的。從這點上看,將其做爲默認操做也是合乎情理的。然而,對於多數類來講,這種判斷並無什麼意義。例如,採用這種方式比較鏈各個PrintStream對象是否相等就徹底沒有意義。然而,常常須要檢測兩個對象狀態的相等性,若是兩個對象的狀態相等,就認爲這兩個對象是相等的。java

    例如,若是兩個僱員對象的姓名、薪水和僱用日期都同樣,就認爲它們是相等的 (在實際的僱員數據庫中,比較ID更有意義。利用下面這個示例演示equals方法的實現技巧)。程序員

class Employee
{
	...
	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 class don't match, they can't be equal
		if (getClass() != otherObject.getClass())
			return false;
		
		// now we know otherObject is non-null Employee
		Employee other = (Employee)otherObject;
		
		// test whether the fields have identical values
		return name.equals(other.name);
			&& sallary == other.salary;
			&& hireDay.equals(other.hireDay);
	}
}
    get Class 方法將返回一個對象所屬的類,有關這個方法的詳細內容稍後進行介紹。在檢測中,只有在兩個對象屬於同一個類時,纔有可能相等。

    在子類中定義equals方法時,首先調用超類的equals。若是檢測失敗,對象就不可能相等。若是超類中的域都相等,就須要比較子類中的實例域。算法

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;
	}
}

相等測試與繼承

   若是隱式和顯式的參數不屬於同一個類,equals方法將如何處理呢?這是一個頗有爭議的問題。在前面的例子中,若是發現類不匹配,equals方法就返回false。可是,許多程序員卻喜歡使用instanceof進行檢測:數據庫

    if(!(otherObject instanceof Employee)) return false;數組

    這樣作不但沒有解決otherObject是子類的狀況,而且還有可能招致一些麻煩。這就是建議不要使用這種方式的緣由所在。Java語言規範要求equals方法具備下面的特徵:ide

    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(y) 也應該返回trueui

    4) 一致性: 若是x和y引用的對象沒有發生變化,反覆調用x.equals(y) 應該返回一樣的結果this

    5) 對於任意非空引用x, x.equals(null) 應該返回false

    這些規則十分合乎情理,從而避免了類庫實現者在數據機構中定位一個元素時還要考慮調用x.equals(y) ,仍是調用y.equals(x)的問題。

    然而,就對稱性來講,當參數不屬於同一個類的時候須要仔細地思考一下。請看下面這個調用:

   e.equals(m)

   這裏的e是一個Employee對象,m是一個Manager對象,而且兩個對象具備相同的姓名、薪水和僱用日期。若是在Employee.equals 中instanceof進行檢測,則返回true。然而這意味着反過來調用:

   m.equals(e)

也須要返回true。對稱性不容許這個方法調用返回false,或者拋出異常。

   這就使得Manager類受到了束縛。這個類的equals方法必須可以用本身與任何一個Employee對象進行比較,而不考慮經理擁有的那部分特有信息!猛然間會讓人感受instanceof測試並非完美無瑕。

   某些書的做者認爲不該該利用getClass檢測,由於這樣不符合置換原則。有一個應用AbstractSet類的equals方法的典型例子,它將檢測兩個集合是否有相同的元素。AbstractSet類有兩個具體子類:TreeSet和HashSet,它們分別使用不一樣的算法實現查找集合元素的操做。不管集合採用何種方式實現,都須要擁有對任意兩個集合進行比較的功能。

   然而,集合是至關特殊的一個例子,應該將Abstaract.equals聲明爲final,這是由於沒有任何一個子類須要重定義集合是否相等的語義 (事實上,這個方法並無被聲明爲final。這樣作,可讓子類選擇更加有效的算法對集合進行是否相等的檢測。)

   下面能夠從兩個大相徑庭的狀況看一下這個問題:

   ● 若是子類可以擁有本身的相等概念,則對稱性需求將強制採用getClass進行檢測

   ● 若是由超類決定相等的概念,那麼就可使用instanceof進行檢測,這樣能夠在不一樣子類的對象之間進行相等的比較。

   在僱員和經理的例子中,只要對應的域相等蠻久認爲兩個對象相等。若是兩個Manager對象所對應的姓名、薪水和僱用日期均相等,而獎金不相等,就認爲它們是不相同的,所以,可使用getClass檢測。

   可是,假設使用僱員的ID做爲相等的檢測標準,而且這個相等的概念適用於全部的子類,就可使用instanceof進行檢測,並應該將Employee.equals聲明爲final。

   √ 註釋: 在標準Java庫中包含150多個equals方法的實現,包括使用instanceof檢測、調用getClass檢測、捕獲ClassCastException或者什麼也不作。

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

   1) 顯示參數命名爲otherObject,稍後須要將它轉換成另外一個叫作other的變量。

   2) 檢測this與otherObject是否引用同一個對象:

   if (this == otherObject == null) return true;

   這條語句只是一個優化。實際上,這是一種常常採用的形式。由於計算這個等式要比一個一個地比較類中的域所付出的代價小得多。

   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) 如今開始對全部須要比較的域進行比較了。使用==比較基本類型域,使用equals比較對象域。若是全部的域都匹配,就須要返回true;不然返回false。

    return field1 == other.field1

         &&field2.equals(other.field2)

         &&...;

    若是在子類中從新定義equals, 就要在其中包含調用super.equals(other)。

   ! 提示: 對於數組類型的域,可使用靜態的Arrays.equals方法檢測相應的數組元素是否相等。

   × 警告:下面是實現equals方法的一種常見的錯誤。能夠找到其中的問題嗎?

public class Employee
{
	public boolean equals(Employee other)
	{
		return name.equals(other.name);
			&& salary == other.salary;
			&& hireDay.equals(other.hireDay);
	}
	...
}
    這個 方法聲明的顯式參數類型是Employee。其結果並無覆蓋Object類的equals方法,而是定義了一個徹底無關的方法。

   從Java SE 5.0開始,爲了不發生類型錯誤,可使用@Override對覆蓋超類的方法進行標記:

       @Override public boolean equals(Object other)

   若是出現了錯誤,而且正在定義一個新方法,編譯器就會給出錯誤報告。例如,假設將下面的聲明添加到Employee中:

      @Override public boolean equals(Employee other)

   就會看到一個錯誤報告,這是由於這個方法並無覆蓋超類Object中的任何方法。


      —— 聲明:此文源自《JAVA 核心技術》(第8版)   5.2.1"Equals方法" — 5.2.2 "相等測試與繼承"


未完待續

    若是太重新定義equals方法,就必須從新定義hashCode方法,以便用戶能夠將對象插入到散列表中。

    Equals與hashCode方法的定義必須一致: 若是x.equals(y)返回true,那麼x.hashCode()就必須與y.hashCode()具備相同的值。例如,若是用定義的Employee.equals比較僱員的ID,那麼hashCode方法就須要散列ID,而不是僱員的姓名或存儲地址。

相關文章
相關標籤/搜索