现在的我不配喊累,因为我一无所有。
1.equals概述
Object类的equals方法用“==”来比较两个对象,因此它的含义是两个对象是否一样,这里的一样指它们是否为同一个对象。
public boolean equals(Object obj) {
return (this == obj);
}
== 与 equals
- ‘==’ 与 equals 最大的区别是: == 是运算符 而 equals 是方法。
- ‘==’ 可以用于基本类型,表示它们的值是否相等,也可以用于引用类型,表示比较的是对象的地址值是否相等;而equals只能用于比较两个对象。
- 如果没有重写equals方法,也就是说该方法是从超类Object继承而来的,那么equals 与 ‘==’ 是完全等价的,都比较的是对象的内存地址。
- 使其按照实际的需求实现的equals方法主要用于判断对象是否等价,也就是说两个对象是逻辑相等的,而不会去关心他们是否为同一个对象的引用。
Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y); // false
2.何时才要覆盖equals方法
很多情况下我们都不必去覆盖equals方法:
- 类的每个实例本质上都是唯一的。如Thread。
- 超类已经覆盖equals方法了,并且从超类继承来的行为对子类也是合适的。
- 不关心类是否提供了“逻辑相等”的测试功能。
- 确定这个类的equals方法永远不会被调用时。
而当一个类需要自己的特有的“逻辑相等”概念,并且逻辑相等不等同于对象相等时,我们就不得不去覆盖Object类的equals方法。例如我们希望两个实例的某些关键域相等时,我们就认为它们两者是逻辑上相等的,如果不覆盖equals方法,继承Object类的equals方法,那么一个实例只能和它自身相等。
3.覆盖equals的通约
一个正确的equals方法的实现,应该满足等价关系。等价关系包含以下几个方面:
- 自反性:对于任何非null的引用值x,x.equals(x)应返回true。
- 对称性:对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。
- 传递性:对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
- 一致性:对于任何非null的引用值x与y,若对象上equals比较中的信息没有被修改,则多次调用x.equals(y)就会一致地返回true或false。
- 非空性:对于任何非空引用值x,x.equal(null)应返回false。
结合这些要求,以下就是实现高质量equals方法:
- 检查是否为同一个对象的引用,如果是直接返回 true;
- 检查是否是同一个类型,如果不是,直接返回 false;
- 将 Object 对象进行转型;
- 判断每个关键域是否相等。
4.重写equals()中的getClass与instanceof
重写equals()方法,检查是否是同一个类型时。用getClass好呢还是instanceof好?我们推荐使用getClass。
instanceof可以判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。
public class EqualsWithExtendsTest {
public static void main(String[] args) {
Employee e1 = new Employee(1,"张三");
People p1 = new People("张三");
System.out.println("e1.equals(p1):" + e1.equals(p1) + "---p1.equals(e1):" + p1.equals(e1));
}
}
class People{
private String name;
public People(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof People)) return false;
People people = (People) o;
return name != null ? name.equals(people.name) : people.name == null;
}
}
class Employee extends People {
private int id;
public Employee(int id,String name) {
super(name);
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
if (!super.equals(o)) return false;
Employee employee = (Employee) o;
return id == employee.id;
}
}
输出:
e1.equals(p1):false---p1.equals(e1):true
解析:
显然,存在继承关系时,在equals方法中使用instanceof违反了equals通约的对称性,这是由于Employee 类的对象可以识别出People对象,但是People识别不出Employee 的对象,也就是 Employee 对象 instanceof People对象 为true,而People对象 instanceof Employee 对象 为 false,所以导致不遵循对称性原则。
解决上述问题可以用组合代替继承:
class EmployeeNew{
private int id;
private People people;
public EmployeeNew(int id, People people) {
this.id = id;
this.people = people;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmployeeNew)) return false;
EmployeeNew that = (EmployeeNew) o;
if (id != that.id) return false;
return people != null ? people.equals(that.people) : that.people == null;
}
}
也可以保留继承关系,使用getClass替代 instanceof (推荐),注意使用getClass之前进行非空判断,而instanceof 会帮你非空判断的。
class People{
private String name;
public People(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
People people = (People) o;
return name != null ? name.equals(people.name) : people.name == null;
}
}
class Employee extends People {
private int id;
public Employee(int id,String name) {
super(name);
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Employee employee = (Employee) o;
return id == employee.id;
}
}
Tips:
- 覆盖equals方法时总要覆盖hashCode方法:Java基础总结:Object类-hashCode方法 中有详细的解释。
- 不要企图让equals方法过于智能。
- 不要将equals声明中的Object对象替换为其他的类型。