本作品采用知识共享署名-非商业性使用-相同方式共享 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方案可以解决继承关系下的对象比较问题,但前提是你必须正确的使用它。