JAVA拾遗 - 如何正确地覆盖equals方法

本文思想来自于Effective Java第二版 3.1章节,实为阅读总结,如果读者有疑惑的地方强烈建议阅读这个章节。

何时覆盖Equals方法

何时不应该覆盖

覆盖equals方法看上去是一件简单的事情,但是很多覆盖的方法都会导致一些隐性的BUG,最容易避免这些问题的方法就是 不覆盖 equals方法,在这种情况下每个类都只与其自身相等,如果满足以下任何一个条件,就不应该覆盖equals方法

  • 类的每个实例在本质上都是唯一的
    对于代表活动实体而不是值的类来说,如Thread等类。Object类提供的equals方法能够实现这些类的正确行为
  • 不关心类是否提供了“逻辑相等”的测试功能
    java.util.Random覆盖了equals,以检查两个Random示例是否产生相同的随机实例,但是设计者并不认为客户需要这些功能,在这种情况下单纯Object的equals方法就足够了
  • 超类已经覆盖了equals方法,而从超类继承过来的equals方法对于子类也是合适的
    大多数的Set都实现从AbstructSet继承equals实现,大多数List实现从AbstructList继承equals实现,大多数Map实现从AbstructMap继承equals实现
  • 类是私有的或者是包级私有的,可以确定其equals方法永远都不会调用
    在这种情况下应该覆盖其equals方法,以防止其被意外调用。如:
@Override
public boolean equals(Object o) {
    throw new AssertionError();//method is never called
}

何时应该覆盖Equals呢?

如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),且超类并没有覆盖equals方法以实现期望的行为,我们就应覆盖equals方法。
比如说“值类”的情况。我们在对比值类的时候,单纯想知道其在逻辑上是否相等,而不想了解他们是否指向同一个对象。同时这样也使得这个类的实例可以被用作map的key,或者集合的元素,使得映射或者集合能够表现出预期的行为。

equals方法的特性

equals方法实现了等价关系(equivalence relation):

  • 自反性(reflexive)—>x != null && x.equals(x) == true
  • 对称性(symmetric)—>if(x != null && x.equals(y))可以推出y.equals(x)
  • 传递性(transitive)—>x != null && x.equals(y) == true && y.equals(z)可以推出x.equals(z)
  • 一致性(consistent)—>对于非null的x、y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用equals返回的一致是true或者一致是false
  • 对于非null的x,x.equals(null)返回值一定是false

John Donne说过,没有哪个类是孤立的。你需要不断地把一个类的实例传递给另外一个类的实例,在传递过程中,很多类的都依赖于是否遵守equals约定!

覆盖的注意事项

  • 1.覆盖时总是要覆盖hashCode方法
  • 2.别想着把equals做得太“智能”
  • 3.千万别把equals方法中的Object参数对象替换成其他类型(从Override变成Overload!)
  • 4.在每次覆盖equals方法都逐一审查equals 的五个约定,就能避免很多隐藏的BUG!

你可能感兴趣的:(java学习,EffectiveJ)