Java Equals方法

Equals方法

    Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。从这点上看,将其作为默认操作也是合乎情理的。然而,对于多数类来说,这种判断并没有什么意义。例如,采用这种方式比较链各个PrintStream对象是否相等就完全没有意义。然而,经常需要检测两个对象状态的相等性,如果两个对象的状态相等,就认为这两个对象是相等的。

    例如,如果两个雇员对象的姓名、薪水和雇用日期都一样,就认为它们是相等的 (在实际的雇员数据库中,比较ID更有意义。利用下面这个示例演示equals方法的实现技巧)。

class Employee
{
	...
	public boolean equals(Object otherObject)
	{
		// a quick test to see if the objects are identical
		if (this == otherObject) return true;
		
		// must return false if the explicit parameter is null
		if (otherObject == null) return false;
		
		// if the class don't match, they can't be equal
		if (getClass() != otherObject.getClass())
			return false;
		
		// now we know otherObject is non-null Employee
		Employee other = (Employee)otherObject;
		
		// test whether the fields have identical values
		return name.equals(other.name);
			&& sallary == other.salary;
			&& hireDay.equals(other.hireDay);
	}
}
    get Class 方法将返回一个对象所属的类,有关这个方法的详细内容稍后进行介绍。在检测中,只有在两个对象属于同一个类时,才有可能相等。

    在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域。

class Manager extends Employee
{
	...
	public boolean equals(Object otherObject)
	{
		if(!super.equals(otherObject)) return false;
		
		// super,equals checked that this and otherObject belong to the same class
		Manager other = (Manager)otherObject;
		return bonus = other.bonus;
	}
}

相等测试与继承

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

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

    这样做不但没有解决otherObject是子类的情况,并且还有可能招致一些麻烦。这就是建议不要使用这种方式的原因所在。Java语言规范要求equals方法具有下面的特征:

    1) 自反性: 对于任何非空引用x,x.equals(x) 应该返回true

    2) 对称性: 对于任何引用x和y,当且仅当y.equals(x) 返回true, x.equals(y) 也应该返回true

    3) 传递性: 对于任何引用x、y和z,如果x.equals(y) 返回true,y.equals(z) 返回true,x.equals(y) 也应该返回true

    4) 一致性: 如果x和y引用的对象没有发生变化,反复调用x.equals(y) 应该返回同样的结果

    5) 对于任意非空引用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对象进行比较,而不考虑经理拥有的那部分特有信息!猛然间会让人感觉instanceof测试并不是完美无瑕。

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

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

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

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

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

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

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

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

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

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

   2) 检测this与otherObject是否引用同一个对象:

   if (this == otherObject == null) return true;

   这条语句只是一个优化。实际上,这是一种经常采用的形式。因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多。

   3) 检测otherObject是否为null,如果为null,返回false、这项检测是很必要的。

   if (otherObject == null) return false;

   4) 比较this与otherObject是否属于同一个类。如果equals的语义在每个子类中有所改变,就使用getClass检测:

   if (getClass() != otherObject.getClass()) return false;

   如果所有的子类都拥有统一的语义,就使用instanceof检测:

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

   5) 将otherObject转换为相应的类类型变量:

   ClassName other = (ClassName) otherObject;

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

    return field1 == other.field1

         &&field2.equals(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中的任何方法。


      —— 声明:此文源自《JAVA 核心技术》(第8版)   5.2.1"Equals方法" — 5.2.2 "相等测试与继承"


未完待续

    如果过重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。

    Equals与hashCode方法的定义必须一致: 如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值。例如,如果用定义的Employee.equals比较雇员的ID,那么hashCode方法就需要散列ID,而不是雇员的姓名或存储地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值