java中,Object类中有一个equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
默认实现,就是两个对象完全相等才返回true,即判断的是引用的地址是否相同。
大多数情况下,我们都不需要覆盖Object的equals方法。但是有时候我们需要从逻辑上判断两个事物是否等同,比如张三的通讯录里存了一个姓名为“王二”的电话号码,李四的通讯录里也存了一个姓名为“王二”的电话号码,两个通讯录里的这两条记录是否相同呢?我们除了比较“王二”这个姓名外,还要比较电话号码是否相同,如果电话号码也相同则就是同一个人的联系方式。虽然这两条记录并没有在同一个通讯录中,就是说没有相同的地址引用,但我们认为这两套记录是等同的。
所以我们通常在类具备特有的“逻辑相等”概念的时候,会重写Object的equals方法,但是重写equals方法有很多我们需要注意的细节。
覆盖equals方法必须遵循的约定
覆盖equals方法必须遵循自反性(reflexive)、对称性(symmetric)、传递性(transitive)、一致性(consistent)、非null对象与null进行equals比较必为false等约定。
- 自反性:a!=null,则a.equals(a)返回true.
- 对称性:a!=null,b!=null,则a.equals(b) == b.equals(a).
- 传递性:a!=null,b!=null,c!=null,如果a.equals(b) == b.equals(c),这a.equals(c).
- 一致性:a!=null,b!=null,如果a,b没有被修改,那么不管调用多少次equals,返回的结果始终只有一个.
- 非null对象与null进行equals比较必为false.
正确的写equals方法的步骤
- 使用“==”操作符检查参数是否是这个对象的引用。
- 检查参数是否为空。
- 检查参数类型是否匹配。
- 把参数转换为正确的类型。
- 对于类中需要进行逻辑判断的属性,检查参数的属性是否与这个对象的属性相同(比较属性也使用对于属性所属类型的equals方法)。
- 编写完成后,验证是否满足对称、传递、一致等特性。
写equals方法需要注意的地方
- 覆盖equals时总要覆盖hashCode方法,避免一些预想不到的错误。
- 不要将equals声明中的Object对象替换为其他类型,因为参数不同的相同方法名是重载而不是重写,这可能让程序运行结果变得难以预料。
用一个实例结束
判断一个用户是否相同,除了用户名还有手机号必须相同,才能认为是同一个用户,重写equals和hashCode并做了简单的自反、对称、传递、一致、null比较等测试。
package cn.lovecto.test;
public class User {
/** 用户名 */
private String name;
/** 手机号 */
private String phone;
public User(String name, String phone) {
super();
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((phone == null) ? 0 : phone.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
//1.使用“==”操作符检查参数是否是这个对象的引用
if (this == obj){
return true;
}
//2.检查参数是否为空
if (obj == null){
return false;
}
//3.检查参数类型是否匹配
if (getClass() != obj.getClass())
return false;
//4.把参数转换为正确的类型
User other = (User) obj;
//5.对于类中需要进行逻辑判断的属性,检查参数的属性是否与这个对象的属性相同
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (phone == null) {
if (other.phone != null)
return false;
} else if (!phone.equals(other.phone))
return false;
return true;
}
public static void main(String[] args) {
System.out.println("test begin");
User a = new User("a", "135xxxxxxxx");
User b = new User("a", "135xxxxxxxx");
User c = new User("a", "135xxxxxxxx");
//自反性
assert a.equals(a);
//传递性
assert a.equals(b) == b.equals(c) ? a.equals(c) : false;
//对称性
boolean result = a.equals(b);
assert result == b.equals(a);
//一致性
for(int i = 0; i < 100; i++){
assert result == a.equals(b);
}
//非null对象与null进行equals比较必为false
assert a.equals(null) == false;
System.out.println("test end");
assert false;
}
}
备注:要使用上面代码中的assert,Jvm参数中加入“-ea”选项。执行结果如下:
test begin
Exception in thread "main" test end
java.lang.AssertionError
at cn.lovecto.test.User.main(User.java:86)