本文涉及到的概念
1.為什么重載equals方法時,要遵守通用約定
2.重載equals方法時,要遵守哪些通用約定
為什么重載equals方法時,要遵守通用約定
Object類的非final方法都有明確的通用約定,這些方法是被設計成被重載的。重載時,如果不遵守通用約定,那么,其它依賴於這些通用約定的類(例如HashMap和HashSet)就無法結合該類一起正常工作----<<effective java>>
quals方法實現了等價關系,重載時要遵守的通用約定:
a.自反性(reflexive) 對於任何非null的引用值x, x.equals(x)必須返回true。
b.對稱性(symmetric) 對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true
c.傳遞性(transitive) 對於任何非null的引用值x,y和z,如果x.equals(y)返回true,並且y.equals(z)返回true,那么x.equals(z)返回true
d.一致性 對於任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)就會一致地返回
true,或者一致地返回false
e.對於任何非null的引用值x,x.equals(null)必須返回false
a.自反性
基本上不會違背這一條規定。如果違背了的話,將一個引用添加到一個集合中,然后,調用集合的contains(x)方法,它會返回false。x.equals(x)不等於true,導致contains(x)方法返回false。
b.對稱性
對於任何非null的引用值x和y, x.equals(y)返回true, y.equals(x)也要返回true
public final class CaseInsensitiveString { private final String s; public CaseInsensitiveString(String s) { if (s == null) throw new NullPointerException(); this.s = s; } // Broken - violates symmetry! @Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); if (o instanceof String) // One-way interoperability! return s.equalsIgnoreCase((String) o); return false; } // This version is correct. // @Override public boolean equals(Object o) { // return o instanceof CaseInsensitiveString && // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); // } public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Polish"); String s = "polish"; System.out.println(cis.equals(s) + " " + s.equals(cis)); } }
可以把上述的例子代碼,代入對稱性公式,CaseInsensitivesString為x, String為y, CaseInsensitivesString為y.
x.equals(y),y.equals(x)都為true,當y是CaseInsensitivesString類型時;
當y為String類型時,y.equals(x),就為false。
String類的equals方法的實現: stringInstance.equals(CanseInsensitivesStringIns),它會返回false,因為x不是String類型
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }
c.傳遞性
equals預定的第三個要求是,如果一個對象等於第二個對象,並且第二個對象又等於第三個對象,則第一個對象一定等於第三個對象。
----<<effective java>>
重寫類的equals方法后,使用x.equals(y), y.equals(z),x.equals(z),去檢驗。如果發現不符合,則違反了該通用約定,那么,就要重新實現。
下面的例子,違反了傳遞性:
父類Point,Point類之間的比較,重寫equals方法,比較內容是否相等,不是比較引用地址是否相等。
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; } // Broken - violates Liskov substitution principle - Pages 39-40 // @Override public boolean equals(Object o) { // if (o == null || o.getClass() != getClass()) // return false; // Point p = (Point) o; // return p.x == x && p.y == y; // } // See Item 9 @Override public int hashCode() { return 31 * x + y; } }
創建一個子類,繼承Point類
import java.awt.Color; public class ColorPoint extends Point { private final Color color; public ColorPoint(int x, int y, Color color) { super(x, y); this.color = color; } // Broken - violates symmetry! //x為Point,y為ColorPoint // x.equals(y)為true,但是y.equals(x)為false @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; return super.equals(o) && ((ColorPoint) o).color == color; } // Broken - violates transitivity! // @Override public boolean equals(Object o) { // if (!(o instanceof Point)) // return false; // // // If o is a normal Point, do a color-blind comparison // if (!(o instanceof ColorPoint)) // return o.equals(this); // // // o is a ColorPoint; do a full comparison // return super.equals(o) && ((ColorPoint)o).color == color; // } //p1.euqlas(p2),p2.equals(p3),p1.equals(p3) //輸出結果:輸出結果 true, true ,false public static void main(String[] args) { // First equals function violates symmetry Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED); System.out.println(p.equals(cp) + " " + cp.equals(p)); // Second equals function violates transitivity ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3)); } }
p1和p2的比較,不考慮顏色;p2和p3的比較,也不考慮顏色;p1和p3的比較考慮顏色,於是p1和p3不相等。違反了傳遞性原則,x.equals(y),y.equals(z).x.equals(z)
如何解決這個問題?
“
事實上,這是面向對象語言中關於等價關系的一個基本問題。我們無法在擴展可實例化的類的同時,既增加新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象所帶來的優勢。
”
也就是,我們無法在擴展父類,然后,同時在子類中保留父類和子類的equals約定。
解決辦法:
a.復合優先於繼承
b.父類是抽象類(沒有任何值組件),子類添加值域
使用復合的方式來解決
// Simple immutable two-dimensional integer point class - Page 37 import java.util.*; 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; } // See Item 9 @Override public int hashCode() { return 31 * x + y; } } public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } // Adds a value component without violating the equals contract - Page 40 public class ColorPoint { private final Point point; private final Color color; public ColorPoint(int x, int y, Color color) { if (color == null) throw new NullPointerException(); point = new Point(x, y); this.color = color; } /** * Returns the point-view of this color point. */ public Point asPoint() { return point; } @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); } @Override public int hashCode() { return point.hashCode() * 33 + color.hashCode(); } } ///////////////////////////////////////////////////////////////////////// public class Test { public static void main(String[] args) { // First equals function violates symmetry Point p = new Point(1, 2); ColorPoint cp = new ColorPoint(1, 2, Color.RED); System.out.println(p.equals(cp) + " " + cp.equals(p)); // Second equals function violates transitivity ColorPoint p1 = new ColorPoint(1, 2, Color.RED); Point p2 = new Point(1, 2); ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE); System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3)); } } 輸出結果: false false false false false