Object类中的equals方法用于检测一个对象是否等于另外一个对象。在Object类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。从这点看,将其作为默认操作也是合乎情理的。然而,对于多数类来说,这种判断并没有什么意义。例如,采用这种方式比较两个PrintStream对象是否相等就完全没有意义。然而,经常需要检测两个对象状态的相等性,如果两个对象的状态相等,据认为这两个对象是相等的。
equals方法
例如,如果两个雇员对象的姓名、薪水和雇佣日期都一样,就认为它们是相等的。
package equals;
import java.time.LocalDate;
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, LocalDate hireDay) {
super();
this.name = name;
this.salary = salary;
this.hireDay = hireDay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public LocalDate getHireDay() {
return hireDay;
}
public void setHireDay(LocalDate hireDay) {
this.hireDay = hireDay;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((hireDay == null) ? 0 : hireDay.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
long temp;
temp = Double.doubleToLongBits(salary);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) //判断是否为相同引用
return true;
if (obj == null) //判断obj是否为空
return false;
if (getClass() != obj.getClass()) //判断两个对象所属的类是否相同
return false;
Employee other = (Employee) obj;
return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
}
}
为了防备 name 或 hireDay 可能为 null 的情况,需要使用Objects.equals方法。如果两个参数都为 null,Objects.equals(a, b) 调用将返回 true;如果其中一个参数为 null,则返回 false;否则,如果两个参数都不为 null,则调用 a.equals(b)。
在子类中定义equals方法时,首先调用超类的equals。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例。
package equals;
import java.time.LocalDate;
public class Manager extends Employee {
public Manager(String name, double salary, LocalDate hireDay, double bonus) {
super(name, salary, hireDay);
this.bonus = bonus;
}
private double bonus;
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(bonus);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj)) //调用超类的equals比较
return false;
if (getClass() != obj.getClass())
return false;
Manager other = (Manager) obj;
if (Double.doubleToLongBits(bonus) != Double.doubleToLongBits(other.bonus))
return false;
return true;
}
}
相等测试与继承
如果隐式和显示的参数不属于同一个类,equals 方法将如何处理?这是一个很有争议的问题。在前面的例子中,如果发现类不匹配,equals方法就返回一个false。但是,我们许多人却喜欢使用 instanceof 进行检测:
if(!otherObject instanceof Employee)
return false;
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。
此时再考虑上面的例子:
e.equals(m)
这里的 e 是一个Employee对象, m 是一个manager对象,并且两个对象具有相同的姓名、薪水和雇佣日期。如果在Employee.equals中用instanceof进行检测,则返回true。然而这意味着反过来调用:
m.equals(e)
也需要返回true。对称性不允许这个方法调用返回false,或者抛出异常。
这就使得Manager类收到了束缚。这个类的equals方法必须能够用自己与任何一个Employee对象进行比较,而不考虑经理(Manager)拥有的那部分特有信息!所以说instanceof测试并不是完美无瑕。
下面可以从两个截然不同的情况看一下这个问题:
- 如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass 进行检测。
- 如果由超类决定相等的概念,那么就可以使用 instanceof 进行检测, 这样可以在不同子类的对象之间进行相等的比较。
在雇员和经理的例子中,只要对应的域相等,就认为两个对象相等。如果两个Manager 对象所对应的姓名、薪水和雇佣日期均相等,而奖金不相等,就认为它们是不相同的,因此,可以使用getClass检测。
但是,假设使用雇员的ID作为相等的检测标准,并且这个相等的概念适用于所有的子类,就可以使用 instanceof 进行检测,并应该将 Employee.equals 声明为 final。
综上所述,下面给出一个完美的 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() != otherObjcet.getClass()) return false; - 如果所有的子类都有统一的语义,就是用 instanceof 检测。
if (!(otherObject instanceof ClassName)) return false;
将 otherObject 转换为响应的类类型变量。
ClassName other = (ClassName) otherObjcet;现在开始对所有需要比较的域进行比较。使用 == 比较基本类型域,使用 equals 比较对象域。如果所有域都匹配,就返回 true;否则返回 false。