equals和hashCode方法
equals和hashCode都是Object对象中的非final方法,它们设计的目的就是被用来覆盖(override)的,所以在程序设计中还是经常需要处理这两个方法的。而掌握这两个方法的覆盖准则以及它们的区别还是很必要的,相关问题也不少。
不被重写的equals和hashCode方法
equals方法
Object类中自带的equals方法是判断一个对象的引用是否等于另一个对象的引用
1 | public boolean equals(Object obj) { |
hashCode方法
它是一个本地方法。它的默认实现是返回内存地址换算而来的一个值。
1 | public native int hashCode(); |
为什么要重写equals和hashCode方法
在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等。在这种情况下,原生的equals方法就不能满足我们的需求了。
而重写hashCode方法则是因为有一些关于Object.hashCode的约定(来自《Effective Java》第三版第11条):
- 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有修改,那么对同一个对象的多次调用,hashCode方法都必须始终返回同一个值。在一个应用程序与另一个应用程序的执行过程中,执行hashCode方法所返回的值可以不一致
- 如果两个对象根据equals方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果
- 如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode必须产生不同的结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
如果只重写了equals方法而没有重写hashCode方法的话,则会违反约定的第二条:相等的对象必须具有相等的散列码(hashCode)
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。
重写hashCode的几点提示
- 把某个非零常数值,比如说17(最好是素数),保存在一个叫result的int类型的变量中。
- 把某个非零常数值,比如说17(最好是素数),保存在一个叫result的int类型的变量中。
- 为该域计算int类型的散列码c:
- 如果该域是boolean类型,则计算(f?0:1)。
- 如果该域是byte、char、short或者int类型,则计算(int)f。
- 如果该域是float类型,则计算Float.floatToIntBits(f)。
- 如果该域是long类型,则计算(int)(f ^ (f>>>32))。
- 如果该域是double类型,则计算Double.doubleToLongBits(f)得到一个long类型的值,然后按照步骤4,对该long型值计算散列值。
- 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals的方式来比较这个域,则同样对这个域递归调用hashCode。如果要求一个更为复杂的比较,则为这个域计算一个“规范表示”,然后针对这个范式表示调用hashCode。如果这个域的值为null,则返回0(或者其他某个常数)
- 如果该域是一个数组,则把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据步骤下面的做法把这些散列值组合起来。
- 按照下面的公式,把步骤1中计算得到的散列码C组合到result中:
result = 31*result+c。
- 为该域计算int类型的散列码c:
- 返回result。
- 写完hashCode方法之后,问自己“是否相等的实例具有相等的散列码”。如果不是的话,找出原因,并修改。