继承关系下的equals改写

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方案可以解决继承关系下的对象比较问题,但前提是你必须正确的使用它。

正确改写equals的解决方案

03-08

在Java语言中有一些恒久的话题,比如equals的改写问题。rnJosh Bloch在Effective Java一书中为我们提供了一个很好的改写equals方法的模板。但他的方案没有解决继承关系下两种对象的比较问题。Martin Odersky等三人在2009年6月1日发表的[url=http://www.artima.com/lejava/articles/equality.html]How to Write an Equality Method in Java[/url]一文中,为我们解决了这个问题,[url=http://coolshell.cn]酷壳[/url]的[url=http://coolshell.cn/?p=1051]如何在Java中避免equals方法的隐藏陷阱[/url]一文是它的中文版。在这里我结合了 Effective Java一书中提到的相关描述并借鉴了Martin Odersky的canEqual方法,重新整理出一套行之有效的改写equals的方案。敬请指正!rnrn----------------------------------rn[size=14px]实现canEqual的注意事项[/size]rnrn首先需要指出的是,新改写方案主要依赖于canEqual方法,canEqual要完成的任务是:rn [i]判断当前对象(this)可以与哪些类型的目标对象进行内容上的比较[/i]rn实现canEqual的方式比较自由,只要能完成上述任务即可。但在实现过程中需要注意:rnrn[b]1. canEqual的声明[/b]rncanEqual方法的正确声明是:rn [i]public boolean canEqual(Object other);[/i]rn其中参数 other就是目标对象,其类型为Object类。rnrn[b]2. canEqual方法不能抛出任何异常。[/b]rn这一要点还可以解释为:使用instanceof而不是getClass方法来检查实参的类型。原因在于实参为null的场合下instanceof可以返回false,而getClass方法将会抛出NullPointException。rnrn[b]3. 确保实例化目标对象所用的类也存在canEqual方法[/b]rn通常情况下,内容的比较来自于同一种类型的两个对象之间;特殊情况下可以看到来自于具有继承关系的两类对象之间的内容比较。针对于这两种情况,可以使用instanceof关键字实现canEqual方法。这样做同样可以确保实例化目标对象所用的类一定存在canEqual方法。rn如果我们使用其它方式实现canEqual方法的话,请认真检查实例化目标对象的类是否存在 canEqual方法。rnrn[size=14px]改写equals的步骤[/size]rnrn1. 使用==操作符检查“实参是否为指向对象的一个引用”。如果是的话,则返回true。rn2. 使用当前对象(this)的canEqual方法检查是否可以与目标对象(实参)进行比较。如果不是的话,则返回false。rn3. 使用instanceof操作符(注1)检查“实参是否为正确的类型”。如果“正确”的类型不止一个,按优先级(注 2)依次检查每一个“正确”的类型。如果所有的“正确”类型都不是,则返回false。rn4. 针对每一个符合要求的“正确”类型,做以下处理:rn a. 把实参转换到“正确”的类型。因为前面已经有了instanceof的检查,所以这个转换可确保成功。rn b. 使用目标对象(转换类型之后的实参)的canEqual方法检查是否可以与当前对象(this)进行比较。如果不是的话,则返回 false(注3)。rn c. 对于该类中每一个“关键”域,检查实参中的域与当前对象中对应的域值是否匹配。检查完毕将结果直接返回(注3)。rn5. 当你改写equals方法之后,问自己一个问题:equals处理的“正确”类型是否与canEqual方法中允许的“正确”类型等价(注 4)?rnrn[size=14px]改写equals的注意事项[/size]rnrn[b]1. 使用instanceof而不是getClass方法来检查实参的类型。[/b]rn原因参考实现canEqual的注意事项。rnrn[b]2. 正确处理多个“正确”类型的检查优先级。[/b]rn当一个对象可以与多种类型的对象进行内容的比较时,类型检查的先后顺序可以根据业务的要求决定。通常情况下首先应该检查是否与自身类型相符合,其次检查其它类型。rnrn[b]3. 对于优先级高的正确类型,优先返回匹配结果。[/b]rn在改写ColorPointEx的 equals时(关于ColorPointEx的描述,请参考[url=http://blog.csdn.net/darxin/archive/2010/02/27/5331137.aspx]继承关系下的equals改写[/url]一文),正确类型有两个:继承自ColorPointEx的类与继承自Point的类,并且ColorPointEx的检查优先级高于Point。当一个目标对象在优先级高的类型(如ColorPointEx类型)比较下出现关键域内容不匹配的情况时,是否还需要进行下一个正确类型(如Point类型)的内容比较呢?回答是不需要。rnrn[b]4. equals中处理的“正确”类型必须与canEqual中允许的“正确”类型等价。[/b]rn以ColorPointEx为例,如果改写的canEqual中允许的“正确”类型为继承自Point的所有类,但改写的equals中只处理了继承自 ColorPoint的所有类这一种“正确”的类型,这种改写将会违反对称性原则。rnrn----------------------------------rn希望这一方案可以帮助大家正确改写equals方法。由于篇幅问题在这里没有给出具体的例子。如果大家想深入了解请阅读下面三篇文章,希望对大家的理解有所帮助。rn[url=http://blog.csdn.net/darxin/archive/2010/02/20/5312743.aspx]利用 Commons Lang库改写equals与hashCode方法[/url]rn[url=http://blog.csdn.net/darxin/archive/2010/02/27/5331137.aspx]继承关系下的equals改写[/url]rn[url=http://blog.csdn.net/darxin/archive/2010/03/06/5351228.aspx]如何正确改写equals方法[/url]rnrn参考:rn[url=http://www.artima.com/lejava/articles/equality.html]How to Write an Equality Method in Java[/url]rn[url=http://coolshell.cn/?p=1051]如何在Java中避免equals方法的隐藏陷阱[/url]

没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试