继承关系下的equals改写

来源:互联网 发布:如何使用数据有效性 编辑:程序博客网 时间:2024/05/21 10:13

Creative Commons License
本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议 进行许可。

 

 

学习过Java的人都知道,Java对象的内容比较依靠的是Object类的equals方法。改写这个方法有严格的要求,JDK API中是这样描述的:


public boolean equals(Object obj);

指示其他某个对象是否与此对象“相等”。


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,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 x,x.equals(null) 一定返回 false。

注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

Josh Bloch在Effective Java一书中介绍了一种通用的改写equals的方法,Apache Commons Lang库也为我们提供了两个工具类来简化改写equals与hashCode的过程。关于这一点请参考利用Commons Lang库改写equals与hashCode方法一文。

具有继承关系的对象比较

五大特性中的对称性与传递性是其中比较麻烦的两个,特别是当我们要进行子类对象与父类对象的内容比较时。如下面的例子:
父类代码(Point类)


子类代码(ColorPoint类)


枚举型Color


当你想要对Point与ColoredPoint两种对象进行内容比较时,上述代码将会违反对称性原则。测试代码如下:


那么,如何才能正确比较两个具有继承关系的对象呢?

Josh Bloch的解决方案

 

Josh Bloch在Effective Java一书中提供的解决方案是:复合优先于继承。也就是避开了继承问题,使ColorPoint与Point之间没有继承关系,进而完全不可比。ColorPoint类的改造如下:


两种对象的比较结果均为false,测试代码如下:


Josh Bloch为我们提供了避免错误的方法,但没有解决我们提出的问题!

Martin Odersky的解决方案

与Josh Bloch的解决方案相对应,Martin Odersky、Lex Spoon与Bill Venners三人在合作完成的How to Write an Equality Method in Java 一文中提出了另一个解决方案:canEqual 方法。此文的中文版可查看酷壳翻译整理的如何在Java中避免equals方法的隐藏陷阱 。本质上这个方案提供一个判断函数,判断哪些对象可以与本类对象进行比较。
据此,Point类的实现如下:


ColorPoint类的实现如下:


测试过程如下:


Martin的方案很好的解释了为什么ColorPoint对象不能与Point对象进行比较,但这似乎并不能解决如何才能让ColorPoint对象与Point对象进行比较的问题。利用canEqual是否可以完成这一目的呢?

补充说明

在这里提供另一个类ColorPointEx,实现了可以与所有继承自Point的对象进行比较的功能。并且比较是有选择的:如果比较的对象也是继承自ColorPointEx,则比较的内容包括Color信息;如果比较的对象是继承自 Point,则比较的内容不包括Color信息。ColorPointEx的代码实现如下:


测试代码如下:


可以说,Martin的canEqual方案可以解决继承关系下的对象比较问题,但前提是你必须正确的使用它。

原创粉丝点击