今天来看提示十:覆盖equals时需要遵守的通用约定。
当equals没有被覆盖时,比较的是内存中的地址,类的每个实例都只和自身相等,当满足下面条件时,我们不会去覆盖equals方法:
- 每个类的实例都是固有唯一的。
- 不太关心类是否提供了逻辑相等的测试功能。
- 父类已经重写了 equals 方法,则父类行为完全适合于该子类。
- 类是私有的或包级私有的,可以确定它的 equals 方法永远不会被调用。
接下来就是重写equals时需要注意的五个原则:
- 自反性: 对于任何非空引用 x, x.equals(x) 必须返回 true。
- 对称性: 对于任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true。
- 传递性: 对于任何非空引用 x、y、z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 必须返回 true。
- 一致性: 对于任何非空引用 x 和 y,如果在 equals 比较中使用的信息没有修改,则x.equals(y) 的多次调用必须始终返回 true 或始终返回 false。
- 非空性:对于任何非空引用 x, x.equals(null) 必须返回 false。
这五点看起来非常直接,我一开始也感觉这些都是理所应当。但是作者介绍了继承和equals的情况后我就有了不一样的想法,难怪父类重写了equals方法后,子类就不推荐重写了。
比如A类有一个属性a,重写了equals只要a相等,那么对象相同。B类继承A,并且多了一个b属性,想当然,它的equals方法就需要比较a,b两个属性。这样做的话,我们就会发现A.equals(B)没有问题,但是B.equals(A)就是false了。如果为了解决这对称性的问题,我们在B的equals方法中进行分类,如果对象是A,那么就只比较a属性,如果对象是B,那么就比较a,b属性,否则就返回false。这样做看似解决了对称性问题,但是当我们引入B1,B2两个不同的B对象,那么就容易得到B1.equals(A),A.equals(B2),但是B1和B2又显然不相同,这样又违反了传递性。所以当我们遇到这种情况时,作者推荐组合而非继承。通过组合的方式可以有效解决这个问题。
试图解决对称性会引入传递性问题以下是编写高质量 equals 方法的配方:
- 使用
==
操作符检查参数是否为这个对象的引用, 如果是, 则返回true。 - 使用
instanceof
操作符检查参数是否为正确的类型, 如果不是, 则返回false. 注意这个地方不能使用getClass
替换instanceof
,这样不符合里氏替换原则。 - 把参数转换成正确的类型。
- 对于该类中的每个关键域, 检查参数中的域是否与该对象中对应的域相匹配。
现在idea等工具都提供了一键生成equals等方法,我以前也用过,但是需要注意equals的格式是可以自己选择的,有时候会生成带有getClass的方法,可能我们需要注意一下。
最后是三条提醒:
- 当重写 equals 方法时,同时也要重写 hashCode 方法。
- 不要让 equals 方法试图太聪明。
- 在 equal 时方法声明中,不要将参数 Object 替换成其他类型。因为这样是重载,并不是覆盖重写。应该养成良好的习惯,在覆盖方法的时候加上@Override注解,及时发现这些错误。
总之,除非必须:在很多情况下,不要重写 equals 方法,从 Object 继承的实现完全是你想要的。如果你确实重写了 equals 方法,那么一定要比较这个类的所有重要属性,并且以保护前面 equals 约定里五个规定的方式去比较。
网友评论