Effective java读书笔记 第八条
覆盖equals方法其实是非常有讲究的, 许多覆盖方式会导致错误, 并且带来非常严重的后果. 首先看一下文档上对equals方法是怎么说的吧
自反性, 对称性, 传递性, 有木有想起离散数学=-=
首先看一下哪些情况是不用覆盖equals方法的, 这样的话, 类的每个实例都只与它自己相等.
类的每个实例本质上都是唯一的
什么意思呢, 就是说这个类表示活动实体, 而不是值, 就不需要去覆盖equals了, Object提供的实现就已经足够了. 比如说Thread, GUI的控件等等不关心类是否提供逻辑相等的测试功能
比如说java.util.Random覆盖了equals, 用来检查两个Random实例产生的随机数序列是否相同, 其实这并没有什么卵用啊, Object对于equals的实现就够啦超类已经覆盖了equals, 对于子类也适用*
这种情况无需再去实现equals
如果类具有自己特有的逻辑相等概念, 而超类的的equals并不是期望的实现的话, 就需要我们去覆盖equals了.
需要覆盖equals的时候就需要遵守上面截图的规范啦:
-
自反性
自反性说明一个对象是等于其自身, 自己和自己相等, Object的也就实现了这个了:
publicbooleanequals(Objectobj){
return(this==obj);
}
我就是我, 是颜色不一样的烟火
-
对称性
简单来说, 就是a=b成立的话, 那么b=a必定成立.
来看个例子, 有一个CaseInsensitiveString类, 这个类比较的时候会忽略大小写:
final class CaseInsensitiveString{
private final String s;
public CaseInsensitiveString(String s) throws NullPointerException{
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, 判断s与CaseInsensitiveString类的s或者和String忽略大小后是否相等, 否则就返回false, 然后我们来测试一下,
CaseInsensitiveString cis1 = new CaseInsensitiveString("boom!");
CaseInsensitiveString cis2 = new CaseInsensitiveString("Boom!");
String s = "BoOM!";
System.out.println(cis1.equals(cis2));
System.out.println(cis1.equals(s));
}
输出正如我们所料, 都是true, 但是别忘了自反性啊, cis1.equals(s)输出true, s.equals(cis1)也应该是输出true的, 事实上s.equals(cis1)
的结果是false. 显然违反了对称性, String类的equals并不知道不区分大小写的CaseInsensitiveString类, 因此s.equals(cis1)
返回了false.
为了解决这个问题, 只要将企图与String互操作的那段代码从equals中删除就行了
@Override
public boolean equals(Object o) {
return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
-
传递性
最复杂的就是传递性了, 离散中最麻烦的也是求传递闭包了.
传递性的意思也很简单的, 就是a=b, b=c的话, 那么a和c也是相等的.
有一个Point类, 用来表示坐标点
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 x == p.x && y == p.y;
}
}
然后又有一个ColorPoint类, 来表示带颜色的点
class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
}
没有复写equals的情况下, 虽然ColorPoint和Point作比较的时候能饭后正确的结果, 但是两个ColorPoint之间做比较的时候忽略了颜色信息, 这显然不是我们想要的结果, 于是乎:
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) {
return false;
}
return super.equals(o) && ((ColorPoint) o).color == color;
}
}
这样对了吗? 抱歉, 问题还是很大, 虽然实现了两个有色点之间的比较, 但是当普通点和有色点比较的时候, 违反了对称性, 普通点和有色点比较会忽略颜色, 而有色点和普通点则总是返回false.
继续改:
@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;
}
如果o不是ColorPoint, 就用o去比较this, 这样就会忽略颜色信息了, 测试一下:
ColorPoint p1 = new ColorPoint(1, 1, Color.RED);
Point p2 = new Point(1, 1);
ColorPoint p3 = new ColorPoint(1, 1, Color.GREEN);
System.out.println(p1.equals(p2));
System.out.println(p2.equals(p3));
返回的都是true, 很好, 对称性的问题解决了, 等等, 这里不是在讨论传递性吗!!!按照传递性来说, p1=p2, p2=p3, 所以p1和p3肯定是相等啊喂, 但是这里很明显就是不相等的, 大家又不是色盲.
这样写呢
@Override
public boolean equals(Object o) {
if (o == null || o.getClass() != getClass()) {
return false;
}
Point p = (Point)o;
return x == p.x && y == p.y;
}
只有当对象相同时才 比较, 这样虽然解决了问题, 但是却不是我们想要的解决方法, 来看一个更好的实现, 用复合代替继承:
class ColorPoint {
private final Color color;
private final Point point;
public ColorPoint(int x, int y, Color color) {
if (color == null) {
throw new NullPointerException();
}
point = new Point(x, y);
this.color = color;
}
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);
}
}
一致性
如果两个对象是相等的, 就应该保持一直是相等的, 除非这两个对象中有一个或者两个都被修改了, 所以记住: 相等的对象永远相等, 不相等的对象永远不相等.非空性
所有的对象都不能为null, 尽管很难想象什么情况下o.equals(null)
会返回true. 但是意外抛出NullPointerException异常的可能却不难想象, 所以可以这样写来不允许抛出NullPointerException异常
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
}