本作品采用知识共享署名-非商业性使用-相同方式共享 2.5 中国大陆许可协议进行许可。
在继承关系下的equals改写一文中,我简单介绍了Martin Odersky提出的canEqual方案,此方案可以正确改写基于继承关系下的equals方法。本文结合Effective Java一书中提到的相关描述并借鉴canEqual方法,重新整理出一套行之有效的改写equals的方案。敬请指正!
首先需要指出的是,新改写方案主要依赖于canEqual方法,canEqual要完成的任务是:
判断当前对象(this)可以与哪些类型的目标对象进行内容上的比较
实现canEqual的方式比较自由,只要能完成上述任务即可。但在实现过程中需要注意:
1. canEqual的声明
canEqual方法的正确声明是:
public boolean canEqual(Object other);
其中参数 other就是目标对象。
2. canEqual方法不能抛出任何异常。
这一要点还可以解释为:使用instanceof而不是getClass方法来检查实参的类型。原因在于实参为null的场合下instanceof可以返回false,而 getClass方法将会抛出NullPointException。如使用instanceof的实现:
public class Point { // 代码略 public boolean canEqual(Object other) { // other为null的场合,判断返回false。 return (other instanceof Point); } }
使用getClass方法的实现:
public class Point { // 代码略 public boolean canEqual(Object other) { // 不使用getClass方法的原因: // 原因之一,other对象为null的场合,下面的调用将抛出NullPointException; // 原因之二,other对象被限制为Point一种类型,子类对象将无法与父类对象比较。 return (Point.class.equals(other.getClass())); } }
3. 确保实例化目标对象的类也存在canEqual方法
通常情况下,内容的比较来自于同一种类型的两个对象之间;特殊情况下可以看到来自于具有继承关系的两类对象之间的内容比较。针对于这两种情况,可以使用instanceof关键字实现 canEqual方法。这样做同样可以确保实例化目标对象所用的类一定存在canEqual方法。
但如果我们使用其它方式实现canEqual方法的话,请认真检查实例化目标对象的类是否存在canEqual方法。
1. 使用instanceof而不是getClass方法来检查实参的类型。
原因参考实现canEqual的注意事项。
2. 正确处理多个“正确”类型的检查优先级。
当一个对象可以与多种类型的对象进行内容的比较时,类型检查的先后顺序可以根据业务的要求决定。通常情况下首先应该检查是否与自身类型相符合,其次检查其它类型。
3. 对于优先级高的正确类型,优先返回匹配结果。
在改写ColorPointEx的equals时(关于ColorPointEx的描述,请参考继承关系下的equals改写一文),正确类型有两个:继承自ColorPointEx的类与继承自Point的类,并且ColorPointEx的检查优先级高于Point。当一个目标对象在优先级高的类型(如ColorPointEx类型)比较下出现关键域内容不匹配的情况时,是否还需要进行下一个正确类型(如Point类型)的内容比较呢?回答是不需要。如下例:
ColorPointEx p1 = new ColorPointEx(1, 2, Color.RED); ColorPointEx p2 = new ColorPointEx(1, 2, Color.BLUE); boolean result = p1.equals(p2);
此处我们应该不会再对两个对象进行Point类型的内容比较了。
4. equals中处理的“正确”类型必须与canEqual中允许的“正确”类型等价。
以ColorPointEx为例,如果改写的canEqual中允许的“正确”类型为继承自Point的所有类,但改写的equals中只处理了继承自ColorPoint的所有类这一种“正确”的类型,这种改写将会违反对称性原则。
作为单一类型比较的例子,正确改写Point的equals方法为:
import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; public class Point { private final int x; private final int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } @Override public boolean equals(Object other) { // 使用==操作符检查“实参是否为指向对象的一个引用”。 if (other == this) { return true; } // 使用当前对象(this)的canEqual方法检查 // 是否可以与目标对象(实参)进行比较。 if (!this.canEqual(other)) { return false; } // 使用instanceof操作符检查“实参是否为正确的类型”。 if (other instanceof Point) { // 把实参转换到正确的类型。 Point that = (Point) other; // 使用目标对象(转换类型之后的实参)的canEqual方法检查 // 是否可以与当前对象(this)进行比较。 if (!that.canEqual(this)) { return false; } // 对于该类中每一个“关键”域, // 检查实参中的域与当前对象中对应的域值是否匹配。 return new EqualsBuilder() .append(this.getX(), that.getX()) .append(this.getY(), that.getY()) .isEquals(); } // 所有的“正确”类型都不是,返回false。 return false; } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(this.getX()) .append(this.getY()) .toHashCode(); } public boolean canEqual(Object other) { // Point对象可以与所有继承自Point的对象进行比较 return (other instanceof Point); } }
作为多类型比较的例子,正确改写ColorPointEx的equals方法为:
import org.apache.commons.lang.builder.EqualsBuilder; public class ColorPointEx extends Point { private final Color color; public ColorPointEx(int x, int y, Color color) { super(x, y); this.color = color; } public Color getColor() { return color; } @Override public boolean equals(Object other) { if (other == this) { return true; } if (!this.canEqual(other)) { return false; } // 注1:使用instanceof而不是getClass方法来检查实参的类型。 // 注2:正确处理多个“正确类型”的检查优先级。 if (other instanceof ColorPointEx) { // ColorPointEx对象之间的比较 ColorPointEx that = (ColorPointEx) other; if (!that.canEqual(this)) { // 注3:对于优先级高的正确类型,优先返回匹配结果。 return false; } // 注3:对于优先级高的正确类型,优先返回匹配结果。 return new EqualsBuilder() .appendSuper(super.equals(that)) .append(this.getColor(), that.getColor()) .isEquals(); } // 注2:正确处理多个“正确类型”的检查优先级。 if (other instanceof Point) { // Point对象之间的比较 Point that = (Point) other; if (!that.canEqual(this)) { // 注3:对于优先级高的正确类型,优先返回匹配结果。 return false; } // 注3:对于优先级高的正确类型,优先返回匹配结果。 return new EqualsBuilder() .appendSuper(super.equals(that)) .isEquals(); } // 所有的“正确”类型都不是,返回false。 return false; } }