/**
*改写equals方法很容易导致严重的错误,如果满足下面任何一个条件,那么避免问题的最容易的方法
*就是不改写equals方法,这样每个实例只与它自己相等
*(1)一个类的每个实例本质上是唯一的。
*(2)不关心一个类是否提供了“逻辑相等“的测试功能。
*(3)超类改写了equals,从超类继承过来的行为对于子类也是合适的。
*(4)一个类是私有的,或者是包级私有的,并且可以确定它的equals方法永远也不会被调用。
*关于(4),为了以防万一有一天它被调用,最好以如下方式改写:
*publicbooleanequals(Objecto){
*thrownewUnsupportedOperationException();
*}
*/
/**
*Q:何时改写Object.equals呢?
*A:当一个类有自己特有的“逻辑相等”概念,而且超类也没有改写equals以实现期望的行为时,
* 这通常适用于值类(valueclass)的比较,如Integer或Date,这样做使得这个类的实例
* 可以被用做映射表(map)的键(key),或者集合(set)的元素,并使map或set表现出预期的行为
*注意:但是有一种值类——类型安全枚举类型不要求改写equals方法,因为它能保证每一个值至多只存
* 在一个对象,所以Object的equals方法等同于逻辑意义上的equals方法
*/
/**
*Q:在改写equals方法时,遵守那些通用约定?
*A:equals方法在非空对象引用上实现相等关系:
* (1)自反性(reflexive):对于任何非空引用值x,x.equals(x)都应返回true。
* (2)对称性(symmetric):对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,
* x.equals(y)也应返回true。
* (3)传递性(transitive):对于任何非空引用值x、y和z,如果x.equals(y)返回true,
* 并且y.equals(z)返回true,那么x.equals(z)应返回true。
* (4)一致性(consistent):对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,
* 前提是对象上equals比较中所用的信息没有被修改.
* (5)对于任何非空引用值x,x.equals(null)都应返回false。
*/
////////////////////如果违反以上的约定""""""""""""""""""""""""
//(1)自反性(reflexive)
//如果想把一个类的实例加入到collection中,该集合果断的告诉你,该集合不包含刚刚你加入的实例
//(2)对称性(symmetric)
/**
*Case-insensitivestring.Caseoftheoriginalstringis
*preservedbytoString,butignoredincomparisons.-Page27
*/
publicfinalclass CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if (s == null)
thrownew NullPointerException();
this.s = s;
}
//违反对称性,CaseInsensitiveString的equals方法知道普通的String对象,反之则不然
/*
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;
}
*/
//修改后的
publicboolean equals(Object o) {
return o instanceof CaseInsensitiveString &&
((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}
//注意:改写equals时总是要改写hashCode
publicstaticvoid main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s));
System.out.println(s.equals(cis));
}
}
public class Color {
private String name;
private Color(String name) {
this.name = name;
}
public static Color RED = new Color("red");
public static Color GREEN = new Color("red");
public static Color BLUE = new Color("red");
}
//(3)传递性(transitive)
class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
//注意:改写equals时总是要改写hashCode
}
public class ColorPoint extends Point{
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// 违反对称性,当比较一个普通点和一个有色点以及反过来的情况时,前者忽略颜色信息,后者总会返回false
//如:Point p = new Point(1,2); ColorPoint cp = new ColorPoint(1,2,Color.RED);
// p.equals(cp)返回true;cp.equals(p)返回返回false;
/*
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color == color;
}
*/
//修改后的,满足了对称性,违反了传递性
//如:ColorPoint p1 = new ColorPoint(1,2,Color.RED);
//Point p2 = new Point(1,2);
//ColorPoint p3 = new ColorPoint(1,2,Color.Blue);
//p1.equal(p2)和p2.equals(p3)都返回true,但是p1.equals(p3)返回false
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
ColorPoint cp = (ColorPoint)o;
return super.equals(o) && cp.color == color;
}
//注意:改写equals时总是要改写hashCode
//Entry
public static void main(String[] args) {
//对称性测试
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp));
System.out.println(cp.equals(p));
System.out.println();
// 传递性测试
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2));
System.out.println(p2.equals(p3));
System.out.println(p1.equals(p3));
}
}
/**Q:如何解决在扩展一个可实例化类的同时,既要增加新的特征,同时保留equals约定?
* A:没有一个简单的办法可以做到!!
* 参照Item14:复合优于继承,这个问题还有有好的解决办法。
* 注意:(1) java.sql.Timestamp对java.util.Date进行了子类化,并且增加了nanosecond域,
* Timestamp的equals方法确实违反了对称性,如果混合使用Date和Timestap对象,就可能出现不正确行为!!!
* (2)只要不可能创建超类的实例(如在抽象类的子类中增加新的特征),就不会违反equals方法的约定,
*/
public class NewColorPoint {
private Point point;
private Color color;
public NewColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = color;
}
//Returns the point-view of this color point.
public Point asPoint() {
return point;
}
public boolean equals(Object o) {
if (!(o instanceof NewColorPoint))
return false;
NewColorPoint cp = (NewColorPoint)o;
return cp.point.equals(point) && cp.color.equals(color);
}
//注意:改写equals时总是要改写hashCode
public static void main(String[] args) {
//对称性测试
Point p = new Point(1, 2);
NewColorPoint cp = new NewColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp));
System.out.println(cp.equals(p));
System.out.println();
// 传递性测试
NewColorPoint p1 = new NewColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
NewColorPoint p3 = new NewColorPoint(1, 2, Color.BLUE);
System.out.println(p1.equals(p2));
System.out.println(p2.equals(p3));
System.out.println(p1.equals(p3));
}
}
class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
//注意:改写equals时总是要改写hashCode
}
//(4)一致性,对于两个对象的对象,除非有一个对象(或两个都)被修改,那么它们必须始终相等
// 对于不可变对象,那么必须满足equals方法满足:相等的对象永远相等,不相等的对象永远不相等. 晕!
//(5)非空性,意思是所有的对象都不能等于null,通用约定不允许抛出NullPointerException,
// 因此必须通过null测试:
// public boolean(Object o){
// if(o==null) return false; }
// 更好的方法:
// public boolean equals(Object o){
// if(!(o instanceof MyType)) return false;}
/**总之,要实现高质量的equals方法,应该:
* (1)使用==操作符检查“实参是否为指向对象的一个引用” (性能优化)
* (2)使用instanceof操作符检查“实参是否为正确的类型”
* (3)把实参转换到正确的类型
* (4)对于该类中的每一个“关键”域,检查实参中的域与当前对象中对应的域值是否匹配
* (5)编写完equals之后应该检查是否满足:对称性,传递性,一致性
*
* 改写equals方法的一些“告诫”:
* (1)当你改写equals的时候,总要改写hashCode
* (2)不要企图让equals方法过于聪明,加入太多等价关系比较只会让事情变糟糕
* (3)不要使equals方法依赖于不可靠的资源,否则将非常困难
* (4)不要将equals声明中的Object对象替换为其他类型,即不要将equals(Object o)改为equals(MyClass c)
* 该方法overload(重载)而不是override(改写)了Object的equals,
* 只有它们返回同样的结果(但没必要)时才可以接受
*/