Effective Java - 注意覆盖equals

平时很难遇到需要覆盖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直接使用"=="比较。
  • 回过头来重新检查一遍:是否满足自反性、对称性、传递性和一致性。
时间: 2024-12-11 12:05:52

Effective Java - 注意覆盖equals的相关文章

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

任何覆盖了equals方法的类都需要覆盖hashCode方法. 忽视这一条将导致类无法与基于散列的数据结构一起正常工作,比如和HashMap.HashSet和Hashtable. 下面是hashCode相关规范: ·在程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个对象调用多少次hashCode,起结果必须始终如一地返回同一个证书. 如果是同一个程序执行多次,每次调用的结果可以不一致. ·如果两个对象根据equals方法比较是相等的,那么两个对象的hashCo

Effective Item 5 - 覆盖equals方法时需要注意

多数情况下,我们不会去覆盖equals方法. 什么时候不需要覆盖equals? ·类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. ·超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. ·当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言

Effective Java - 谨慎覆盖clone

覆盖clone时需要实现Cloneable接口,Cloneable并没有定义任何方法. 那Cloneable的意义是什么? 如果一个类实现了Clonable,Object的clone方法就可以返回该对象的逐域拷贝,否则会抛出CloneNotSupportedException. 通常,实现接口是为了表明类的行为. 而Cloneable接口改变了超类中protected方法的行为. 这是种非典型用法,不值得仿效. 好了,既然覆盖了clone方法,我们需要遵守一些约定: x.clone() != x

Java hashCode() 和 equals()的若干问题

原文:http://www.cnblogs.com/skywang12345/p/3324958.html 本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equals() 之间有什么联系? 第1部分 equals() 的作用 equals() 的作用是 用来判断两个对象是否相等. equals() 定义在JDK的Object.java中.通过判断两个对

Java hashCode() 和 equals()的若干问题解答&lt;转载自skywang12345&gt;

本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equals() 之间有什么联系? 第1部分 equals() 的作用 equals() 的作用是 用来判断两个对象是否相等. equals() 定义在JDK的Object.java中.通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等.源码如下: public boolean equ

Java hashCode() 和 equals()的若干问题解答

第1部分 equals() 的作用 equals() 的作用是 用来判断两个对象是否相等. equals() 定义在JDK的Object.java中.通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等.源码如下: public boolean equals(Object obj) { return (this == obj); } 既然Object.java中定义了equals()方法,这就意味着所有的Java类都实现了equals()方法,所有的类都可以通过equals(

覆盖equals时请遵守通用约定——Effective Java 读书笔记

如果满足了以下任一条件,这就正是所期望的结果: 类的每个实例本质上都是唯一的. 不关心类是否提供了"逻辑相等"的测试功能. 超类已经覆盖了equals,从超类继承过来的行为对于子类也是适合的. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用. 如果类具有自己特有的"罗吉相等"概念(不同于对象等同的概念),而且超类还,没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. equals方法实现了等价关系: 自反性 对称性 传递

Effective Java 第三版——10. 重写equals方法时遵守通用约定

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 10. 重写equals方法时遵守通用约定 虽然Object是一个具体的类,但它主要是为继承而设计的.它的所有非 final方法(equals.hashCode.toStr

【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法

1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> *