在开始编写文章前,有几个问题需要思考一下:
- equals 和 hashCode 方法是什么?
- equals 方法具有哪些特性?
- equals 和 hashCode 间的通用约定
1. equals 和 hashCode 方法是什么?
equal 方法用于检测一个对象是否等于另一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。然而,对于大多数类来说,这样的判断并没有什么意义,经常需要检测两个对象状态的相等性,如果两个对象的状态相等,就认为这两个对象是相等的。所以这个时候需要重写 equals 方法,来满足业务上的需求。下面是默认的 equals 方法实现:
public boolean equals(Object obj) {
return (this == obj);
}
hashCode 方法由对象导出的一个整型值(散列码)。散列码没有规律。在 Object 类中,默认的散列码为对象的存储地址。
2. equals 方法具有哪些特性?
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。
3. equals 和 hashCode 间的通用约定
看一下 Object.hashCode 的通用约定:
- 在一个应用程序执行期间,如果一个对象的 equals 方法做比较所用到的信息没有被修改的话,那么对该对象调用 hashCode 方法多次,它必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,这个整数可以不同,即这个应用程序这次执行返回的整数与下一次执行返回的整数可以不一致。
- 如果两个对象根据 equals(Object) 方法是相等的,那么调用这两个对象中任一个对象的 hashCode 方法必须产生同样的整数结果。
- 如果两个对象根据 equals(Object) 方法是不相等的,那么调用这两个对象中任一个对象的 hashCode 方法,不要求必须产生不同的整数结果。然而,程序员应该意识到这样的事实,对于不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。
如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中。
扩展
下面给出编写一个的 equals 方法的建议:
- 显示参数命名为 otherObject,稍后需要将它转换成另一个叫做 other 的变量。
- 检测 this 与 otherObject是否引用同一个对象。
- 检测 otherObject 是否为 null,如果为 null,返回 false。这项检测是很有必要的。
- 比较 this 与 otherObject 是否属于同一个类。如果 equals 的语义在每个子类中有所改变,就是用 getClass 检测。如果所有的子类都拥有统一的语义,就是用 instanceof 检测。
- 将 otherObject 转换为相应的类类型变量。
- 现在开始对所有需要比较的域进行比较了。使用 == 比较基本类型域,使用 equals 比较对象域。如果在子类中重新定义 equals,就要在其中包含调用 super.equals。
public class Employee {
@Override
public boolean equals(Object otherObject) {
// 2
if(this == otherObject) return true;
// 3
if(otherObject == null) return false;
// 4
//if(!(otherObject instanceof Employee)) return false
if(getClass() != otherObject.getClass()) return false;
// 5
Employee other = (Employee) otherObject;
// 6
return name.equals(other.name)
&& salary.equals(((Employee) otherObject).salary)
&& hireDay.equals(((Employee) otherObject).hireDay);
}
}
对于使用 Class 还是 instanceof 来作比较,可以从两个截然不同的情况看下这个问题:
- 如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass 进行检查。
- 如果由超类决定相等的概念,那么就可以使用 instanceof 进行检测,这样可以在不同子类的对象之间进行相等的比较。
下面给出编写一个的 hashCode 方法的建议:
a、把某个非零常数值,保存在一个叫 result 的 int 类型的变量中。
b、对于对象中每一个关键域(值 equals 方法中考虑的每一个域),完成一些步骤:
1、为该域计算 int 类型的散列码 c:
1.1、如果该域是 boolean 类型,则计算(f ? 0:1)。
1.2、如果该域是byte、char、short或者int类型,则计算(int)f。
1.3、如果该域是float类型,则计算Float.floatToIntBits(f)。
1.4、如果该域是long类型,则计算(int)(f ^ (f>>>32))。
1.5、如果该域是double类型,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后按照步骤4,对该long型值计算散列值。
1.6、如果该域是一个对象引用,并且该类的 equals 方法通过递归调用 equals 的方式来比较这个域,则同样对这个域递归调用 hashCode。如果要求一个更为复杂的比较,则为这个域计算一个 "规范表示",然后针对这个范式表示调用 hashCode。如果这个域的值为 null,则返回 0(或者其他某个常数)。
1.7、如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据下面的做法把这些散列值组合起来。
2、按照下面的公式,把步骤 1 中计算得到的散列码 c 组合到 result 中:result = 31 * result + c。
c、返回 result。
d、验证 "是否相等的实例具有相等的散列码"。
可以通过org.apache.commons.lang.builder.HashCodeBuilder这个工具类来方便的重写hashCode方法。