重寫equals方法(未完)
equals方法是我們日常編程中很常見的方法,Object中對這個方法的解釋如下:
boolean |
equals(Object obj) 指示其他某個對象是否與此對象“相等”。 |
查看該方法的底層代碼如下:
public boolean equals(Object obj) { return (this == obj); }
通過上面的代碼很容易就能看出來,Object的equals方法上是用來比較兩個實例是否為同一個實例對象,底層還是依賴於==的判斷。
2.4.1.1 什麽情況下無需覆蓋 equals方法
Equals方法看起來是個很簡單的方法,但是重寫equals方法還是很容易出現問題的,並不是說每個類都需要來重寫equals方法。滿足下面任意條件的類都可以不用覆蓋equals方法;
- l 類的每個實例本質上都是唯一的,如枚舉,單例等;
- l 不關心類是否提供了“邏輯相等”的測試功能。如Random覆蓋equals,檢查兩個Random實例是否產生相同的隨機數序列,但是這其實是很不必要的,所以從Object中繼承來的equals方法已經足夠了。
- l 超類已經覆蓋了equals方法,從超類集成過來的行為對於子類也是合適的。如List實現從ArrayList
- l 類是私有的或者包訪問權限的,可以確定它的equals方法永遠不會被調用。
通過上面的敘述,知道了只要滿足四種情況,那麽無疑覆蓋equals方法,既是多余也容易出現問題,我們應該加以避免。
2.4.1.2 覆蓋equals方法
了解了無需覆蓋equals的條件,那麽什麽條件中覆蓋equals方法呢?覆蓋equals需要註意什麽呢?
當類具有自己特有的“邏輯相等”概念(不同於對象等同的概念),而且超類還沒有覆蓋equals方法以實現期望的行為時,就需要覆蓋equals方法。
上面通常屬於“值類(value class)
但是Java中有一種值類卻不在這一要求之中,那就是枚舉。在覆蓋equals方法時,必須遵循以下的通用約定:
- l 自反性(reflexive):對於任何非null的引用值x,x.equals(x)必須返回true。
- l 對稱性(symmetric):對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
- l 傳遞性(transitive):對於任何非null的引用值x,y和z,如果x.equals(y)返回true,且y.equals(z)返回true,那麽x.equals(z)也必須返回true。
- l 一致性(consistent):對於任何非null的引用值x和y,只有equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)總會一致地返回相同的結果。
- l 非空性(Non-nullity):對於任何非null的引用值x,x.equals(null)必須返回false。
這些約定看起來很簡單,但是在實際編程中很容易違反這些約定,一旦違反這些約定,程序運行就會不正常,接下來通過例子展示違反約定的情況以及如何避免這些錯誤。
自反性
自反性:如果我們書寫的equals方法形式的功能不是用於判斷自己的對象是否等於自身(或是自身包含的值),那麽該會是多麽可怕的事情呢?如List中存放實例,然後List判斷contains()該對象時,那麽就會永遠都會返回false;通過代碼來驗證一下:
package cn.object.override.equals; import java.util.ArrayList; import java.util.List; import org.junit.Test; public class EqualsDemo { @Test public void test(){ EqualsDemo demo1 = new EqualsDemo(); EqualsDemo demo2 = new EqualsDemo(); List<EqualsDemo> list = new ArrayList<>(); list.add(demo1); System.out.println("list是否包含demo1:"+list.contains(demo1)); System.out.println("list是否包含demo2:"+list.contains(demo2)); } @Override public boolean equals(Object obj) { // TODO Auto-generated method stub return false; } }
我們通過ArrayList的底層代碼知道contains是通過調用當前類的indexOf方法來確定的,而indexOf是通過循環遍歷判斷參數和集合中的元素是否相等,而這個判斷是通過實例的equals方法來確定的,所以上面的兩個判斷都是false也就可以理解了,這就是equals不正確覆蓋帶來的嚴重後果了;
對稱性
對稱性也是很重要的一個要求,並且邏輯思維也應該是這樣的,那麽如果我們不正常的覆蓋,導致情況不是這樣的會怎麽樣呢?通過一個代碼來實現;
package cn.object.override.equals; import org.junit.Test; /** * @author YorickYou * Code Address:https://github.com/YorickYou/JavaSE.git */ public class CaseInsensitiveString { private final String s; //有參構造 public CaseInsensitiveString(String s) { if (s == null) throw new NullPointerException(); this.s = s; } @Override public boolean equals(Object obj) { if(obj instanceof CaseInsensitiveString) return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s); if (obj instanceof String) return s.equalsIgnoreCase((String)obj); return false; } public static void main(String[] args) { CaseInsensitiveString string1 = new CaseInsensitiveString("abc"); String str = "abc"; //兩次調用equals方法,前者是調用的CaseInsensitiveString的equals方法,後者是String的equals方法 System.out.println(string1.equals(str)+"自反性"+str.equals(string1)); } }
上面對equals方法的覆蓋就嚴重違反了對稱性.如果我們往集合中添加該對象,那麽此時會出現什麽情況呢?
public static void test1(){ List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>(); CaseInsensitiveString string1 = new CaseInsensitiveString("abc"); String str = "abc"; list.add(string1); System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1)); }//true--false--true
所以在覆蓋equals方法時,我們一定要註意對稱性,對於上面問題的解決只需要將String的互操作這一段從equals中移除即可.
package cn.object.override.equals; import java.util.ArrayList; import java.util.List; import org.junit.Test; /** * * @author YorickYou * Code Address:https://github.com/YorickYou/JavaSE.git */ public class CaseInsensitiveString { private final String s; //有參構造 public CaseInsensitiveString(String s) { if (s == null) throw new NullPointerException(); this.s = s; } public static void main(String[] args) { CaseInsensitiveString string1 = new CaseInsensitiveString("abc"); String str = "abc"; //兩次調用equals方法,前者是調用的CaseInsensitiveString的equals方法,後者是String的equals方法 System.out.println(string1.equals(str)+"自反性"+str.equals(string1)); test1(); } @Override public boolean equals(Object obj) { // TODO Auto-generated method stub return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString)obj).s.equalsIgnoreCase(s); } public static void test1(){ List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>(); CaseInsensitiveString string1 = new CaseInsensitiveString("abc"); String str = "abc"; list.add(string1); System.out.println(string1.equals(str)+"--"+list.contains(str)+"--"+list.contains(string1)); } }
重寫equals方法(未完)