上一篇博客讲到Object类中的equals方法用于检测一个对象是否等于另外一个对象。
在Object类中这个方法将判断两个方法是否具有相同的引用,如果两个对象具有相同的引用,他们一定是相等的,从这点上看,将其作为默认的操作也合乎情理,然而
对于大多数类来说,这种判断并没有什么意义。
例如:采用这种方式比较两个PrintStream对象是否相等就完全没有意义,
然而,经常要检测两个对象状态的相等性,如两个对象的状态相等,就认为两个对象是相等的。
例如:两个雇员对象的姓名、薪水和雇佣日期都一样,就认为他们是相等的(在实际的雇员数据库中,比较id更有意义。利用下面这个示例演示equals方法的实现机制)
class Employee {
private Long id;
private String name;
private int age;
private BigDecimal salary;
private Date hireDay;
...//省略set和get
// 重写equals
public boolean equals(Object obj) {
// 引用地址相等
if (this == obj) {
return true;
}
// 判断是否为空
if (obj == null) {
return false;
}
// 判断类是否匹配
if (getClass() != obj.getClass()) {
return false;
}
Employee employee = (Employee) obj;
// return name.equals(employee.name) && age == employee.age
// && salary.equals(employee.salary)
// && hireDay.equals(employee.hireDay);
//为了方位引用类型的如:name salary hireDay 可能为null 要使用Objects.equals方法如果两个参数都为null
//Objects.equals(a,b)调用将返回true,如果其中一个参数为null将返回false,否则两个参数都不为null,则调用
//a.equals(b);
return Objects.equals(name, employee.name)
&& age == employee.age
&&Objects.equals(salary, employee.salary)
&& Objects.equals(hireDay,employee.hireDay);
}
}
class Test {
public static void main(String[] args) {
Employee e = new Employee(1L, "zhangsan", 18, new BigDecimal(15345.67),new Date());
Employee e2 = new Employee(2L, "zhangsan", 18,new BigDecimal(15345.67), new Date());
System.out.println(e.equals(e2));// false,没有重写超类的equals方法
System.out.println(e.equals(e2));// true,重写超类的equals方法
}
}
提示:API java.util.Objects 7
/**
* Returns {@code true} if the arguments are equal to each other
* and {@code false} otherwise.
* Consequently, if both arguments are {@code null}, {@code true}
* is returned and if exactly one argument is {@code null}, {@code
* false} is returned. Otherwise, equality is determined by using
* the {@link Object#equals equals} method of the first
* argument.
*
* @param a an object
* @param b an object to be compared with {@code a} for equality
* @return {@code true} if the arguments are equal to each other
* and {@code false} otherwise
* @see Object#equals(Object)
*/
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
下面进入本博客的主题:相等测试与集成
如果隐式和显式的参数不属于同一个类,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(z)也应当返回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是一个Manage对象 ,并且两个对象具有相同的属性和属性值。
如果在Employee.equals中用instanceof进行检测 则返回true,
Employee.equals
// 断类是否匹配
/*if (getClass() != obj.getClass()) {
return false;
}*/
boolean bool = false;
if (obj instanceof Employee) {
Employee employee = (Employee) obj;
bool = Objects.equals(name, employee.name) && age == employee.age;
}
if (obj instanceof Manage) {
Manage eManage = (Manage) obj;
bool = Objects.equals(name, eManage.getName())
&& age == eManage.getAge();
}
return bool;
然而这意味着反过来调用
m,equals(e) 也需要返回true 对称性不允许这个方法调用返回false 或者抛出异常
这使得Manage受到了束缚 这个类的方法必须能够用自己的equals方法与任何一个Employee对象进行比较,而不考虑Manage拥有的那部分特有信息
猛然让人觉得instanceof并不是完美无瑕
某些书的作者认为不应该利用getClass检测,因为这样不符合置换原则,有一个应用AbstractSet类的equals方法的典型例子,他将检测两个集合是否有相同
的元素,AbstractSet类有两个具体的子类 TreeSet 和HashSet ,他们分别使用不同的算法实现查找集合元素的操作,无论集合采用何种方式实现,都需要拥有对任意
两个集合进行比较的功能。
然而集合是相当特殊的例子,应该将AbstractSet.equals声明为final ,这是因为没有任何一个子类需要重定义集合是否相等的语义(事实上这个方法并没有声明为final 这样做,就可以让子类选择更加有效的算法对集合进行是否相等的检测)。
下面从两个截然不同的情况看下这个问题:
1.如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测,
2.如果由超类决定相等的概念,那么就可以使用instanceof 进行检测,这样可以在不同子类的对象之间进行相等的比较。
在Employee 好Manage的例子中只要对应的域相等,就认为两个对象相等。
如果两个Manage对象所对应的姓名,薪水,所雇佣日期相等,年龄不同,就认为他们是不同的,因此可以使用getClass检测,
但是假设使用Employee的id作为相等的检测标准,并且这个相等的概念适用于所有的子类 就可以使用instanceof进行检测,并且应该将Employee.equals声明我final
注释:
在标注java库中包含150多个equals方法的实现,包括使用instanceof检测,调用getClass检测,捕获ClassCastException或者什么也不做,可以查看java.sql.Timestamp
类的API文档,在这里实现人员无不尴尬的指出,他们使自己陷入了困境,Timestamp类继承自java.util.Date,而后者的equals方法使用了一个instanceof 检测,这样一来就无法
覆盖实现equals使之同时做到对称且正确。
下面给出一个完美的equals方法的建议:
1.显示参数命名为otherObject ,稍后将它转换成另一个叫做other的变量
2.检测this与otherObject是否引用同一个对象:
if(this==otherObject)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 filed1 = other.field1&&Objects.equals(field2,other.field2)&&...;
如果子类中重新定义equals,就要在其中包含调用super.equals(other);
提示:对于数组类型的域 可以使用静态的Arrays.equals 方法检测相应的数组元素是否相等
java.util.Arrays
static Boolean equals(type[] a , type[] b)
警告:实现equals方法有一种常见的错误
public boolean equals(Employee other){
.....
}
这个方法声明的显式参数类型为Employee ,其结果根本没有覆盖O bjece类中的equals方法而是定义了一个完全无关的方法
本文大部分是从专业书籍上摘抄的,也是一种形式的笔记