您的位置:首页 > 编程语言 > Java开发

Java核心技术卷I:基础知识(原书第8版):5.2.2 相等测试与继承

2014-10-14 09:56 423 查看
铁文整理

5.2.2
相等测试与继承

如果隐式和显式的参数不属于同一个类,equals方法将如何处理呢?这是一个很有争议的问题。在前面的例子中,如果发现类不匹配,equals方法就返回false。但是,许多程序员却喜欢使用instanceof进行检测:

if (!(otherObject
instanceof Employee))
return
false;

这样做不但没有解决otherObject是子类的情况,并且还有可能会招致一些麻烦。这就是建议不要使用这种处理方式的原因所在。Java语言规范要求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)应该返回同样的结果。

对于任意非空引用x,x.equals(null)应该返回false。

这些规则十分合乎情理,从而避免了类库实现者在数据结构中定位一个元素时还要考虑调用x.equals(y)还是调用y.equals(x)的问题。

然而,就对称性来说,当参数不属于同一个类的时候需要仔细地思考一下。请看下面这个调用:e.equals(m)。这里的e是一个Employee对象,m是一个Manager对象,并且两个对象具有相同的姓名、薪水和雇佣日期。如果在Employee.
equals中用instanceof进行检测,则返回true,然而这意味着反过来调用m.equals(e)也需要返回true。对称性不允许这个方法调用返回false,或者抛出异常。

这就使得Manager类受到了束缚,这个类的equals方法必须能够用自己与任何一个Employee的对象进行比较,而不考虑经理拥有的那部分特有信息!猛然间会让人感觉instaneof测试并不是完美无暇。

某些书的作者认为不应该利用getClass检测,因为这样不符合置换原则。有一个应用AbstractSet类的equals方法的典型例子,它将检测两个集合是否有相同的元素。AbstractSet类有两个具体子类:TreeSet和HashSet,它们分别使用不同的算法实现查找集合元素的操作。无论集合采用何种方式实现,都需要拥有对任意两个集合进行比较的功能。

然而,集合是相当特殊的一个例子,应该将AbstractSet.equals声明为final,这是因为没有任何一个子类需要重定义集合是否相等的语义(事实上,这个方法并没有被声明为final。这样做,可以让子类选择更加有效的算法对集合进行是否相等的检测)。

下面可以从两个截然不同的情况看一下这个问题:

如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测。

如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类的对象之间进行相等的比较。

在雇员和经理的例子中,只要对应的域相等,就认为两个对象相等。如果两个Manager对象所对应的姓名、薪水和雇佣日期均相等,而奖金不相等,就认为它们是不相同的,因此,可以使用getClass检测。

但是,假设使用雇员的ID作为相等的检测标准,并且这个相等的概念适用于所有的子类,就可以使用instanceof进行检测,并应该将Employee.equals声明为final。

注释:在标准Java库中包含150多个equals方法的实现,包括使用instanceof检测、调用getClass检测、捕获ClassCastException或者什么也不做。

下面给出编写一个完美的equals方法的建议:

显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量。

检测this与otherObject是否引用同一个对象:if
(this == otherObject)
return
true;这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。

检测otherObject是否为null,如果为null,返回false。这项检测是很必要的。if
(otherObject == null)
return
false;

比较this与otherObject是否属干同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:if
(getClass() != otherObject.getClass())
return
false;如果所有的子类都拥有统一的语义,就使用instanceof检测:if
(!(otherObject instanceof ClassName))
return
false;

将otherObject转换为相应的类类型变量:ClassName other = (ClassName) otherObject。

现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true,否则返回false:return
field1 == other.field1&& field2 == other.field2 %% ...;

如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。

提示:对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等。

警告:下面是实现equals方法的一种常见的错误,可以找到其中的问题吗?
public
class Employee {

public
boolean equals(Employee other)
{

return name.equals(other.name)&&
salary == other.salary

&& hireDay.equals(other.hireDay);

}
}

这个方法声明的显式参数类型是Employee。其结果并没有覆盖Object类的equals方法,而是定义了一个完全无关的方法。

从Java SE 5.0开始,为了避免发生类型错误,可以使用@Override对覆盖超类的方法进行标记:

@Override
public
boolean equals(Object other)
{

如果出现了错误,并且正在定义一个新方法,编译器就会给出错误报告。例如,假设将下面的声明添加到Employee类中:

@Override
public
boolean equals(Employee other)
{

就会看到一个错误报告,这是因为这个方法并没有覆盖超类Object中的任何方法。


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: