覆盖equals方法需要注意的

Effective java读书笔记 第八条

覆盖equals方法其实是非常有讲究的, 许多覆盖方式会导致错误, 并且带来非常严重的后果. 首先看一下文档上对equals方法是怎么说的吧

覆盖equals方法需要注意的_第1张图片
Object类的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也是相等的.
    覆盖equals方法需要注意的_第2张图片

    有一个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;
    }
}

你可能感兴趣的:(覆盖equals方法需要注意的)