此系列文章为本人对《Effective Java》一书的学习笔记,主要是记录对书中重点内容的理解。
既然有缘看到此文,那么希望能对你有所帮助。
本文对应原书 第10条 覆盖equals方法时请遵守通用约定

不需要重写的情况

首先我们要知道,重写equals方法看起来非常的简单(有了lombok之后几乎无成本),但是很多的重写方式会导致错误的产生,并且后果很严重。

所以避免这类问题的办法其实就是不去重写,让每个类的实例只和自身相等。

来看看在哪些情况下我们不需要重写equals吧:

  • 类的每个实例本质上都是唯一的
    举个栗子,一个代表某种行为活动而不代表值的类,Thread类,它就完全没有重写的必要。
  • 类没有必要提供“逻辑相等”的测试功能
    如Pattern类,它可以去覆盖equals,用来检查两个实例是否代表同一个正则,但设计者觉得用户不需要这个功能,所以没有进行重写。
  • 父类已经重写了equals,而且父类的行为适用于子类
    如大多的Set都从AbstractSet继承equals实现,List从AbstractList继承,Map从AbstractMap继承。父类的equals实现已经完全够用,且适合子类,所以没有必要重写。
  • 类是私有或包级私有的,可以确定它的equals方法永远不会被调用
    如果你非常想规避风险,可以这样重写,一用就报错,以确保它不会被意外调用:
@Override
public boolean equals(Object o) {
	throw new AssertionError();
}

需要重写的情况

当这个类为“值类”的时候

值类:这个类具有逻辑相等的概念,仅仅表示一个或多个值,且父类没有重写equals方法。

如:IntegerString和业务中定义的某些POJO类,我们在比较的时候并不关心指针是否指向同一个对象,我们只想知道它们在逻辑上是否相等。

(P.S. 特殊的值类,如Enum,比较特殊,不在此范围内)

重写的规范

在重写的时候,必须要遵守它的通用约定,以确保程序不会发生严重的错误:

  • 自反性:对于非null引用值x,x.equals(x)必须返回true
  • 对称性:对于非null引用值x、y,当且仅当y.equals(x)为true时,x.equals(y)返回true
  • 传递性:对于非null引用值x、y、z,若x.equals(y)为true,y.equals(z)为true,则x.equals(z)也为true
  • 一致性:对于非null引用值x、y,只要equals进行比较时用到的实例信息没有被修改,那么多次调用的返回值一致;
  • 非空性:对于非null引用值x,x.equals(null)必须返回false

高质量重写的诀窍

  • 使用 == 操作符检查“参数是否可能为这个对象的引用”
    在某些情况下,调用equals方法的成本可能是很高的,那么直接比较地址可能更为有效的得出结果;
  • 使用 instanceof 操作符检查“参数是否为正确的类型”
    不仅可以处理掉null值,也可以为接下来的类型转换规避ClassCastException异常;
  • 把参数转换成正确类型
    因为进行了 instanceof 的判断,所以转换会确保成功;
  • 对于类中的关键属性,检查参数中的属性是否匹配

告诫

  • 重写equals时一定要重写hashcode
  • 别把equals方法搞得太智能,简单做属性的比对就可以了;
  • 别把equals方法中的Object入参替换为其它类型(因为这是重载不是重写);

总结

编写equalshashcode方法是十分繁琐的,所以现在往往用现成的工具,如Google的AutoValue或者lombok,亦或是直接使用IDE自带的代码生成。毕竟人懒,又容易犯错,这些工具不会。

总而言之,不要轻易去重写equals方法,很多情况从Object继承的实现正是你所需要的,而在确定重写时,请一定遵守相应的规范。

水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义