第8条:覆盖equals时请遵守通用约定
覆盖equals方法看起来非常简单,但是很容易导致错误,并且造成严重后果。
那么,什么时候应该覆盖Object.equals呢?一般情况下,如果对于一个类的两个对象,我们想要关注的是它的值是否相等,而不是是否指向同一个类,就可以这么做。但是如果使用了单例模式。
在覆盖equals方法时,应注意以下的规范,这些规范不是必须的,但是如果违反,当其它的对象去使用这个对象时,返回的结果将是不可预期的:
自反性:要求对象必须等于,这应该是最为基本的要求。因为大部分的集合(Collection)中的contains方法都借助equals方法来实现,所以如果违反了这一条规定,那么如果将这个对象添加到集合中后,调用contains方法却会告诉你该集合不包含你刚刚添加的实例。
对称性:对于A与B两个对象,当A.equals(B)为true时,B.equals(A)也为true。有时无意中便会违反这一点:
public final 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 String){
return s.equals((String)s);
}
}
}
如上面这段代码,就会出现这样的问题:
String s = "example";
CaseInsensitiveString cis = new CaseInsensitiveString("example");
cis.equals(s); //true
s.equals(cis); //false
- 传递性:对于A,B,C三个对象,当A.equals(B)为true而且B.equals(C)为true时,A.equals(C)也应为true。当涉及到继承关系时也就可能碰到这种问题:
public class Point {
public int size;
public Point(int size){
this.size = size;
}
@Override
public boolean equals(Object o){
if(!(o instanceof Point)){
return false;
}
Point p = (Point)o;
return p.size == this.size;
}
}
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int size,Color color){
super(size);
this.color = color;
}
@Override
public boolean equals(Object o){
if(!(o instanceof Point)){
return false;
}
if(!(o instanceof ColorPoint)){
return o.equals(this);
}
return super.equals(o) && ((ColorPoint)o).color == color;
}
}
ColorPoint p1 = new ColorPoint(1, Color.RED);
Point p2 = new Point(1);
ColorPoint p3 = new ColorPoint(1, Color.BLUE);
p1.equals(p2); //true
p2.equals(p3); //true
p1.equals(p3); //false
一致性:对于A与B两个对象,如果A.equals(B)为true,那么除非A或者B被修改,否则无论何时调用A.equals(B)都应为true。
非空性:即所有的对象都必须不等于null,这是为了防止NullPointerException的出现。如果在equals方法中使用了instanceof操作符进行类型检查,那么可以省略掉非空判断,因为例如A instanceof B,如果A为null,那么不论B是什么类型都将返回false。
作者在书中提出了覆盖equals方法时的几点建议,其中值得注意的有:
1. 不要企图让equals方法过于智能:如果在equals方法中执行过于复杂的逻辑,那么很容易陷入各种麻烦之中。
- 不要将equals方法中的Object参数替换为其他类型:因为如果这么做了,那实际上并没有覆盖Object.equals()方法,而是重载了这个方法,例如:
public boolean equals(MyClass o){
.......
}
那么如果传入的参数不是MyClass对象或其子类,那么实际上调用的Object.equals()方法。