Effective Java笔记(10)覆盖 equals 时请遵守通用约定

        覆盖 equals 方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重 。 最容易避免这类问题的办法就是不覆盖 equals 方法,在这种情况下,类的每个实例都只与它自身相等。 如果满足了以下任何一个条件,这就正是所期望的结果 :

类的每个实例本质上都是唯一的 。 对于代表活动实体而不是值( value )的类来说确实如此 ,例如 Thread 。Object 提供的 equals 实现对于这些类来说正是正确的行为 。

类没有必要提供“逻辑相等”( logical equality )的测试功能 。 例如,java.util.regex.Pattern 可以覆盖 equals ,以检查两个 Patter口实例是否代表同一个正则表达式,但是设计者并不认为客户需要或者期望这样的功能 。 在这类情况之下,从 Object 继承得到的 equals 实现已经足够了 。

超类已经覆盖了 equals , 超类的行为对于这个类也是合适的 。 例如,大多数的Set 实现都从Abstract Set 继承 equals 实现,List 实现从 AbstractList 继承 equals 实现,Map 实现从 AbstractMap 继承 equals 实现 。

类是私有的 ,或者是包级私有的 , 可以确定它的 equals 方法永远不会被调用 。 如果你非常想要规避风险,可以覆盖 equals 方法,以确保它不会被意外调用:

@Override 
pub1ic boolean equals(Object o) {
    throw new AssertionError(); // Method is never called
}

        在覆盖 equals 方法的时候,必须要遵守它的通用约定 。 下面是约定的内容,来自Object 的规范 。

        equals 方法实现了等价关系( equi va lence relation ),其属性如下:

  1. 自反性( reflexive ) : 对于任何非 null 的引用值 x,x.equals(x )必须返回 true 。
  2. 对称性( symmetric ):对于任何非 null 的引用值 x 和 y ,当且仅当 y.equals(x )返回 true 时,x.equals(y )必须返回 true 。
  3. 传递性( transitive ) : 对于任何非 null 的引用值 x 、y 和 z ,如果 x.equals(y )返回true ,并且 y.equals(z )也返回 true ,那么 x.equals(z )也必须返回 true 。
  4. 一致性( consistent ): 对于任何非 nu ll 的 引用值 x 和 y ,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y )就会一致地返回 true,或者一致地返回 false 。
  5. 对于任何非 null 的引用值 x, x.equals (null )必须返回 false 。

高质量 equals 方法的诀窍 :

1、 使用==操作符检查“参数是否为这个对象的引用” 。 如果是,则返回 true 。 这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做 。

2、使用 instanceof 操作符检查“参数是否为正确的类型” 。 如果不是,则返回 false 。一般说来,所谓“正确的类型”是指 equals 方法所在的那个类 。 某些情况下,是指该类所实现的某个接口 。如果类实现的接口改进了 equals 约定,允许在实现了该接口的类之间进行比较,那么就使用接口 。 集合接口如 Set 、List 、Map 和 Map. Entry 具有这样的特性 。

3、把参数转换成正确的类型 。 因为转换之前进行过且 stanceof 测试,所以确保会成功 。

4、对于该类中的每个“关键”( significant )域,检查参数中的域是否与该对象中对应的域相匹配。 如果这些测试全部成功, 则返回 true ;否则返回 false 。 如果第 2 步 中 的类型是个接口,就必须通过接口方法访问参数中的域;如果该类型是个类,也许就能够直接访问参数中的域,这要取决于它们 的可访问性 。

        有些对象引用域包含null可能是合法的,所以,为了避免可能导致 NullPointerException 异常,则使用静态方法 Objects.equals(Object , Object ) 来检查这类域的等同性 。

        在编写完 equals 方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的 ? 并且不要只是自问,还要编写单元测试来检验这些特性。

下面是最后的一些告诫 :

        覆盖 equals 时总要覆盖 hashCode。

        不要企图让 equals 方法过于 智能 。 如果只是简单地测试域中的值是否相等,则不难做到遵守 equals 约定 。 如果想过度地去寻求各种等价关系, 则很容易陷入麻烦之中 。 把任何一种别名形式考虑到等价的范围内,往往不会是个好主意 。 

        不要将 equals 声 明 中 的 Object 对象替换为其他的类型 。 

        总而言之,不要轻易覆盖 equals 方法,除非迫不得已 。 因为在许多情况下,从Object 处继承的实现正是你想要的 。 如果覆盖 equals ,一定要比较这个类的所有关键域,并且查看它们是否遵守 equals 合约的所有五个条款 。

你可能感兴趣的:(Effective,Java,java,开发语言,后端)