此系列文章为本人对《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方法。
如:Integer
、String
和业务中定义的某些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
入参替换为其它类型(因为这是重载不是重写);
总结
编写equals
和hashcode
方法是十分繁琐的,所以现在往往用现成的工具,如Google的AutoValue或者lombok,亦或是直接使用IDE自带的代码生成。毕竟人懒,又容易犯错,这些工具不会。
总而言之,不要轻易去重写equals
方法,很多情况从Object继承的实现正是你所需要的,而在确定重写时,请一定遵守相应的规范。
水平有限,若文章中存在错误,恳请不吝赐教,这对我以及后面的读者都有重要意义