透過下面的例子掌握equals的用法
package cn.galc.test;public class TestEquals { public static void main(String[] args) { /** * 這裡使用建構方法Cat()在堆內存裡面new出了兩隻貓, * 這兩隻貓的color,weight,height都是一樣的, * 但c1和c2卻永遠不會相等,這是因為c1和c2分別為堆內存裡面兩隻貓的引用對象, *裡面裝著可以找到這兩隻貓的地址,但由於兩隻貓在堆內存裡面存儲在兩個不同的空間裡面, * 所以c1和c2分別裝著不同的地址,因此c1和c2永遠不會相等。 */ Cat c1 = new Cat(1, 1, 1); Cat c2 = new Cat(1, 1, 1); System.out.println("c1==c2的結果是:"+(c1==c2 ));//false System.out.println("c1.equals(c2)的結果是:"+c1.equals(c2));//false }}class Cat { int color, weight, height; public Cat(int color, int weight, int height) { this.color = color; this.weight = weight; this.height = height; }}畫出記憶體分析圖分析c1和c2比較的結果
程式:
Cat c1 = new Cat(1,1,1);Cat c2 = new Cat(1,1,1);
執行完後記憶體之中的佈局如下圖所示,
c1指向一個對象,c2也指向一個對象,c1和c2裡面裝著的是這兩隻Cat對像在堆內存裡面存儲的地址,由於這兩隻Cat對象分別位於不同的存儲空間,因此c1和c2裡面裝著的位址肯定不相等,因此c1和c2這兩個引用物件也肯定不相等。因此執行:「System.out.println(c1==c2);」印出來的結果肯定是false。因此你new出來了兩個對象,你放心,這兩個對象的引用永遠不一樣,一樣的話就會把其中一個給覆蓋掉了,這個可不成。 c1是不是等於c2比較的是c1和c2這兩個引用裡面裝著的內容,因為new出來的兩個物件的它們的引用永遠不一樣,因此c1和c2這兩個引用的內容也永遠不一樣,因此c1永遠不可能等於c2。因此透過比較兩個物件的引用是永遠無法使得兩個物件相等的,一模一樣的。
要判斷兩個物件是否相等,不能透過比較兩個物件的引用是否相等,這是永遠都得不到相等的結果的,因為兩個物件的引用永遠不會相等,所以正確的比較方法是直接比較這兩個對象,比較這兩個對象的實質是不是一樣的,即這兩個對象裡面的內容是不是相同的,通過比較這兩個對象的屬性值是否相同而決定這兩個對像是否相等。
Object類別提供了一個equals()方法來比較兩個物件的內容是否相同,因此我們可以採用這個方法去比較兩個物件是否在邏輯上「相等」。如:c1.equals(c2);這裡是呼叫從Object類別繼承下來的equals()方法,透過查閱API文件得到Object類別裡的equals方法的定義如下:
public boolean equals(Object obj)
在Object這個類別裡面提供的Equals()方法預設的實作是比較目前物件的參考和你要比較的那個引用它們指向的是否是同一個對象,即和「c1==c2」這種寫法是一樣的,「c1.equals(c2)」與「c1==c2」是完全等價的。因此直接使用繼承下來的equals()方法也是無法直接比較兩個物件的內容是否相同的,為此,我們必須得重寫equals()方法,改變這個方法預設的實作。
下面在Cat類別裡面重寫這個繼承下來的equals()方法:
class Cat { int color, weight, height; public Cat(int color, int weight, int height) { this.color = color; this.weight = weight; this.height = height; } /** * 這裡是重寫相等從Object類別繼承下來的equals()方法,改變這個方法預設的實現, * 透過我們自己定義的實現來判斷決定兩個物件在邏輯上是否相等。 * 這裡我們定義如果兩隻貓的color,weight,height都相同, * 那麼我們就認為這兩隻貓在邏輯上是一模一樣的,即這兩隻貓是「相等」的。 */ public boolean equals(Object obj){ if (obj==null){ return false; } else{ /** * instanceof是物件運算子。 * 物件運算子用來測定一個物件是否屬於某個指定類別或指定的子類別的實例。 * 物件運算子是一個組合單字instanceof。 * 該運算符是一個雙目運算符,其左邊的表達式是一個對象,右邊的表達式是一個類, * 如果左邊的對像是右邊的類創建的對象,則運算結果為true,否則為false 。 */ if (obj instanceof Cat){ Cat c = (Cat)obj; if (c.color==this.color && c.weight==this.weight && c.height==this.height){ return true; } } } return false; }}此時在再main方法裡面執行列印的指令:
public static void main(String[] args) { /** * 這裡使用構造方法Cat()在堆內存裡面new出了兩隻貓, * 這兩隻貓的color,weight,height都是一樣的, *但c1和c2卻永遠不會相等,這是因為c1和c2分別為堆內存裡面兩隻貓的引用對象, * 裡面裝著可以找到這兩隻貓的地址,但由於兩隻貓在堆內存裡面存儲在兩個不同的空間裡面, *所以c1和c2分別裝著不同的位址,因此c1和c2永遠不會相等。 */ Cat c1 = new Cat(1, 1, 1); Cat c2 = new Cat(1, 1, 1); System.out.println("c1==c2的結果是:"+(c1==c2 ));//false System.out.println("c1.equals(c2)的結果是:"+c1.equals(c2));//true }這次得到的結果就跟上次沒有重寫equals()方法時得到的結果就不一樣了:
「System.out.println(c1 == c2);」印出來的結果仍然是false,因為這裡是比較兩個物件的引用裡面的內容,這兩個引用裡面的內容當然不相等,而且永遠不會相等,所以印出來的結果一定是false。
「System.out.println(c1.equals(c2));」列印出來的結果為true,因為我們在Cat類別裡面重寫了equals()方法,改變了這個方法預設的實現,我們把方法的實現改為只要這個兩個物件是真的存在,而且都是貓,並且它們的顏色(color),身高(height)和體重(weight)都相同,那麼這兩隻貓在邏輯上就是一模一樣的,是完全相同的兩隻貓,即這兩隻貓是「相等」的。所以這裡印出來的結果是true。
那麼如何比較兩個字串物件是否相等?
看下面的例子:
public class TestEquals { public static void main(String args[]){ String s1 = new String("hello"); String s2 = new String("hello"); System.out.println("s1 == s2的結果是:"+(s1 == s2));//false System.out.println("s1.equals(s2)的結果是:"+s1.equals(s2));//true }}這次是比較兩個字串物件是否相等:
System.out.println(s1 == s2);
印出來的結果仍然是fase,因為這裡比較的是s1和s2兩個字串物件的引用,兩個物件的引用永遠不會相等,所以印出來的結果為false。
System.out.println(s1.equals(s2));
印出來的結果為true,因為在String類別裡面重寫了從Object類別繼承(所有的類別都是從Object類別繼承下來,String類別當然也不例外,從父類別繼承下來就擁有了父類別的一切屬性與方法,所以Sting類別裡面也有equals()方法,並且還把這個繼承下來的equals()方法重寫了)下來的equals()方法,改變了這個方法預設的實現,
在String類別裡面是這樣重寫equals()方法的實作的:用目前的這個字串物件和指定的字串物件比較,指定的字串物件不能為空並且這個物件的字元序列和目前這個字串物件的字串序列一樣,如果這些條件都滿足,那麼這兩個字串物件就是相等的。
因此這裡的s2已經滿足了條件,所以印出來的結果是true。
以後在某一個類別裡面比較兩個物件是否相等時,先去API文檔裡面查找這個類別是否重寫了從Object類別繼承下來的equals()方法。如果重寫了equals()方法,那麼在比較兩個物件是否相等時呼叫的就是重寫以後的equals()方法,如果沒有重寫,那麼呼叫時就是直接呼叫從Object類別裡面的繼承下來的那個equals()方法,並且採用equals()方法預設的實作去比較兩個物件是否相等。因此每一個類別都可以根據需要對從Object類別繼承下來的equals()方法進行重寫。
對於在API文件裡面找某個類,如果一個類別不用引入包就可以直接使用,那麼這個類別一定是在java.lang這個包裡面,如這裡的String類,直接就可以使用了,所以String類別一定是在java.lang這個套件裡面。使用某個類別時看這個類別引入的是哪個包,然後就去這個包裡面找這個類,不用引入包的類一定是位於java.lang裡面,直接去java.lang裡面找就可以了。
一般我們在設計一個類別時,需要重寫父類別的equals方法,在重寫這個方法時,需要依照以下幾個規則設計:
1.自反性:對任意引用值X,x.equals(x)的回傳值一定為true.
2.對稱性:對於任何引用值x,y,當且僅當y.equals(x)回傳值為true時,x.equals(y)的回傳值一定為true;
3.傳遞性:如果x.equals(y)=true, y.equals(z)=true,則x.equals(z)=true
4.一致性:如果參與比較的對象沒有任何改變,則對像比較的結果也不應該有任何改變
5.非空性:任何非空的參考值X,x.equals(null)的回傳值一定為false
例如:
public class People { private String firstName; private String lastName; private int age; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName; } public void setFirstName(String firstName) { this.firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (obj) { if ( this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; People other = (People) obj; if (age != other.age) return false; if (firstName == null) { if (other.firstName != null) return false; } else if (!firstName.equals(other.firstName)) return false; if (lastName == null) { if (其他.lastName != null) return false; } else if (!lastName.equals(other.lastName)) return false; return true; }}
在這個例子中,我們規定一個人,如果姓、名和年齡相同,則就是同一個人。當然你也可以再增加其他屬性,例如必須身分證號相同,才能判定為同一個人,則你可以在equals方法中增加對身分證號的判斷!
總結:比較兩個物件是否相等,我們採用equals()方法,判斷兩個物件是否相等的條件是由我們重寫equals()方法的實作後定義的,這樣就可以比較靈活地使用equals()方法在不同的類別裡面比較位於同一類別下的兩個物件是否相等了。