加入收藏 | 设为首页 | 会员中心 | 我要投稿 宁德站长网 (https://www.0593zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Effective Java -助力覆盖equals

发布时间:2021-11-19 16:53:58 所属栏目:教程 来源:互联网
导读:平时很难遇到需要覆盖equals的情况。 什么时候不需要覆盖equals? 类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的。 超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的。 当确定该类的equ

平时很难遇到需要覆盖equals的情况。
 
什么时候不需要覆盖equals?
 
类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的。
超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的。
当确定该类的equals方法不会被调用时,比如类是私有的。
如果要问什么时候需要覆盖equals?
答案正好和之前的问题相反。
即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为。
(PS:对于枚举而言,逻辑相等和对象相等都是一回事。)
 
既然只好覆盖equals,我们就需要遵守一些规定:
 
自反性 (reflexive):对于任何一个非null的引用值x,x.equals(x)为true。
对称性 (symmetric):对于任何一个非null的引用值x和y,x.equals(y)为true时y.equals(x)为true。
传递性 (transitive):对于任何一个非null的引用值x、y和z,当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true。
一致性 (consistent):对于任何一个非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)的结果依然一致。
(PS:对于任何非null的引用值x,x.equals(null)必须返回false。)
其实这些规定随便拿出一个都是很好理解的。
难点在于,当我遵守一个规定时有可能违反另一个规定。
 
自反性就不用说了,很难想想会有人违反这一点。
 
关于对称性,下面提供一个反面例子:
 
class CaseInsensitiveString {
 
    private final String s;
 
    public CaseInsensitiveString(String s) {
        if (s == null)
            this.s = StringUtils.EMPTY;
        else
            this.s = s;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof CaseInsensitiveString)
            return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
        if (obj instanceof String)
            return s.equalsIgnoreCase((String) obj);
        return false;
    }
 
}
这个例子显然违反对称性,即x.equals(y)为true 但 y.equals(x)为false。
不仅是在显示调用时,如果将这种类型作为泛型放到集合之类的地方,会发生难以预料的行为。
 
而对于上面这个例子,在equals方法中我就不牵扯其他类型,去掉String实例的判断就可以了。
 
关于传递性,即,当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true。
这个规定在对类进行扩展时尤其明显。
 
比如,我用x,y描述某个Point:
 
class Point {
    private final int x;
    private final int y;
 
    public Point(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Point))
            return false;
        Point p = (Point) obj;
        return p.x == x && p.y == y;
    }
 
}
 
现在我想给Point加点颜色:
 
class ColorPoint extends Point {
    private final Color color;
 
    public ColorPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ColorPoint))
            return false;
        return super.equals(obj) && ((ColorPoint) obj).color == color;
    }
 
}
似乎很自然的提供了ColorPoint的equals方法,但他连对称性的没能满足。
 
于是我们加以修改,令其满足对称性:
 
@Override
public boolean equals(Object obj) {
    if (!(obj instanceof Point))
        return false;
    if (!(obj instanceof ColorPoint))
        return obj.equals(this);
    return super.equals(obj) && ((ColorPoint) obj).color == color;
}
 
好了,接下来我们就该考虑传递性了。
比如我们现在有三个实例,1个Point和2个ColorPoint....
然后很显然,不满足<当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true>。
事实上,我们无法在扩展可实例化类的同时,既增加新的值组件,又保留equals约定。
 
于是我索性不用instanceof,改用getClass()。
这个确实可以解决问题,但很难令人接受。
如果我有一个子类没有覆盖equals,此时equals的结果永远是false。
 
既然如此,我就放弃继承,改用复合(composition)。
以上面的ColorPoint作为例子,将Point变成ColorPoint的field,而不是去扩展。 代码如下:
 
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();
    }
}
关于一致性,即如果两者相等则始终相等,除非有一方被修改。
这一点与其说equals方法,到不如思考写一个类的时候,这个类应该设计成可变还是不可变。
如果是不可变的,则需要保证一致性。
 
考虑到这些规定,以下是重写equals时的一些建议:
 
第一步使用"=="操作验证是否为同一个引用,以免不必要的比较操作。
使用instanceof检查参数的类型。
检查所有关键的field,对float和double以外的基本类型field直接使用"=="比较。
回过头来重新检查一遍:是否满足自反性、对称性、传递性和一致性。

(编辑:宁德站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读