若是類具備本身特有的「邏輯相等」概念,並且父類尚未重寫 equals 方法以實現指望的行爲時,就須要重寫 equals 方法。java
這樣作還可使得這個類的實例能夠被用做 Map 的 Key,或者 Set 的元素,使 Map 或 Set 表現出預期的行爲來。ide
在重寫 equals 方法的時候,若是知足瞭如下任何一個條件,就正是所指望的結果:測試
自反性(reflexive):對於任何非 null 的引用值 x,x.equals(x) 必須返回 true;flex
對稱性(symmetric):對於任何非 null 的引用值 x and y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 必須返回 true;this
傳遞性(consistent):對於任何非 null 的引用值 x y z,若是 x.equals(y) 返回 true,而且 y.equals(z) 返回 true,那麼 x.equals(z) 也必須返回true;spa
一致性(consistent):對於任何非 null 的引用值 x and y,只要equals的比較操做在對象中所用的信息沒有被修改,那麼屢次調用 x.equals(y) 都會一致地返回 true,或者一致地返回 false;.net
非空性(non nullity):對於任何非 null 的引用值 x,x.equals(null) 必須返回 false;code
通常不會違背這個原則,若是違背了,那麼出現的現象會是將該類的實例添加到集合後,調用 contains 方法查找這個實例,會獲得一個 false。對象
對稱性簡單說是,任何兩個對象對於它們是否相等的問題都必須保持一致, x 等於 y 那麼 y 也要等於 x。舉個違反這個原則的例子:繼承
package test.ch01; 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 o) { if (o instanceof CaseInsensitiveString) { return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); } if (o instanceof String) { return s.equalsIgnoreCase((String) o); } return false; } }
在這個類中,equals 要作到與普通的字符串比較時不區分大小寫,其問題在於 String 類中的 equals 方法並不知道不區分大小寫,所以反過來比較並不成立,違反了對稱性。
package test.ch01; public class Test { public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Hello"); String s = "hello"; System.out.println(cis.equals(s)); // true System.out.println(s.equals(cis)); // false } }
解決這個問題,只須要把企圖與 String 互操做的代碼從 equals 方法中去掉就能夠了:
package test.ch01; 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 o) { return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); } }
package test.ch01; public class Test { public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Hello"); String s = "hello"; CaseInsensitiveString cis1 = new CaseInsensitiveString("hello"); System.out.println(cis.equals(s)); // false System.out.println(s.equals(cis)); // false System.out.println(cis.equals(cis1)); // true } }
傳遞性要求 x 等於 y,y 等於 z,那麼 x 也要等於 z。可是,此處有很是重要的一點,面嚮對象語言關於等價關係的一個基本問題:
沒法在擴展可實例化的類的同時,即增長新的值組件,同時又保留 equals 約定,除非願意放棄面向對象的抽象所帶來的優點。
考慮兩個類,它們是繼承關係:
package test.ch01; public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) { return false; } Point p = (Point) o; return p.x == x && p.y == y; } }
package test.ch01; public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) { return false; } return super.equals(o) && ((ColorPoint) o).color == color; } }
若是 ColorPoint 類,不重寫 equals,而是直接從 Point 繼承過來,那麼 ColorPoint 與 Point 比較時會忽略掉顏色。
可是若是按照上面代碼,重寫 equals,那麼會違反一致性:
Point p1 = new Point(1, 2); ColorPoint cp1 = new ColorPoint(1, 2, Color.RED); System.out.println(p1.equals(cp1)); // true System.out.println(cp1.equals(p1)); // false
總之面嚮對象語言的等價交換關係是一個問題。
里氏替換原則(Liskov substitution principle)認爲,一個類型的任何重要屬性也將適用於他的子類,所以爲該類型編寫的任何方法,在它的子類型上也應該一樣運行的很好。
雖然沒有一種使人滿意的辦法解決上面的問題,可是仍是有一個不錯的權宜之計:
package test.ch01; public class ColorPoint { private final Point point; private final Color color; public ColorPoint(Point point, Color color) { this.point = point; this.color = color; } @Override public boolean equals(Object o) { if (!(o instanceof Color)) { return false; } ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } }
使用複合方式代替繼承,能夠解決上面的問題。
可變對象在不一樣的時候能夠與不一樣的對象相等,不可變的對象則不會這樣。若是認爲它應該是不可變的,就必須保證 equals 方法知足:相等的對象永遠相等,不想等的對象永遠不相等。
不管類是不是不可變的,都不要使 equals 方法依賴不可靠的資源。
全部對象都必須不等於 null。
1.使用 == 操做符檢查「參數是否爲這個對象的引用」,若是比較操做有可能很昂貴,就值得先這麼作;
2.使用instanceof檢查「參數類型是否正確」;
3.把參數轉換成正確的類型,在instanceof後,因此會保證成功;
4.對每一個關鍵域,檢查參數中的域是否與該對象中對應的域相匹配,float 和 double須要用 Float.compare 和 Double.compare 比較,集合用 Arrays.equals 比較;
5.對於引用域能夠爲 null 的狀況,爲了不致使 NPE,能夠寫成 :
(field == o.filed || (field != null && field.equals(o.field)))
6.最早比較最有可能不一致的域,或者是開銷低的域,最好是同時知足這兩個條件;
7.重寫 equals 時總要重寫 hashCode;
8.不要讓 equals 方法過於智能;
9.不要將 equals 聲明中的 Object 對象替換爲其餘類型;
10.最好用 @Override 註解描述 equals;