Object类中的equals方法用于检测一个对象是否等于另一个对象。在Object类中,这个方法将判断两个对象是否具备相同的引用。若是两个对象具备相同的引用,它们必定是相等的。从这点上看,将其做为默认操做也是合乎情理的。然而,对于多数类来讲,这种判断并无什么意义。例如,采用这种方式比较链各个PrintStream对象是否相等就彻底没有意义。然而,常常须要检测两个对象状态的相等性,若是两个对象的状态相等,就认为这两个对象是相等的。java
例如,若是两个雇员对象的姓名、薪水和雇用日期都同样,就认为它们是相等的 (在实际的雇员数据库中,比较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方法具备下面的特征:ide
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) 也应该返回trueui
4) 一致性: 若是x和y引用的对象没有发生变化,反复调用x.equals(y) 应该返回一样的结果this
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,而不是雇员的姓名或存储地址。