重载equals方法时要遵守的通用约定--自反性,对称性,传递性,一致性,非空性
2017-03-12 21:25 ttylinux 阅读(5369) 评论(0) 编辑 收藏 举报本文涉及到的概念
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
版权声明:
作者:ttylinux
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。