【Effective Java 10.1】覆盖 equals 时请遵守通用约定


1. 不需要覆盖 equals 方法的情况

覆盖 equals 方法看起来很简单,但是有许多覆盖方式会导致错误,并且后果非常严重。最容易避免这类问题的办法就是不覆盖 equals 方法,在这种情况下,类的每个实例都只与其自身相等。以下情况不需要覆盖 equals 方法

  • 类的每个实例本质上都是唯一的:对于代表 “活动实体” 而非 “值” 类,例如 Thread
  • 类没有必要提供 ”逻辑相等“ 的测试功能:例如,java.util.regex.Pattern 可以覆盖 equals,以检查两个 Pattern 实例是否代表同一个正确表达式,但是设计者并不认为客户需要或者期望这样的功能。在这类情况之下,从 Object 继承得到了 equals 实现已经足够了
  • 超类已经覆盖了 equals,超类的行为对于这个类也是合适的。例如,大多数的 Set 实现都从 AbstractSet 继承 equals 实现,List 实现从 AbstractList 继承 equals 实现,Map 实现从 AbstractMap 继承 equals 实现。
  • 类是私有的,或者是包级私有,可以确定它的 equals 方法永远不会被调用

2. 需要覆盖 equals 方法的情况

对于大部分 ”值“ 类对象都需要覆盖 equals 方法。例如 IntergerString。有时甚至需要同时覆写 hashCode 方法。但有一种值类不需要覆盖 equals 方法,即实例受控 ”确保对象只存在一个“(如单例对象,枚举类)

3. 覆盖 equals 方法时的约定

  • 自反性(reflexive):对于任何非 null 的引用值。x.equals(x) 始终为真
  • 对称性(symmetric):对于任何非 null 的引用值 xy。当且仅当 y.equals(x) 返回 true 时,x.equals(y) 必须返回 true
  • 传递性(transitive):对于任何非 null 的引用值 xyz。如果 x.equals(y) 返回 true 时,且 y.equals(z) 也返回true,则x.equals(z)也必须返回 true
  • 一致性(consistent):对于任何非 null 的引用值 xy,只要 equals 的比较操作在对象中所有的信息没有被修改,多次调用 x.equals(y) 就会一致地返回 true,或者一致地返回 false
  • 对于任何非 null 的引用值 xx.equals(null) 必须返回 false

如果违反了这些规定,就会发现程序将会表现得不正常,甚至崩溃,而且很难找到程序的 Bug 所在。