为什么重写equals()方法时,要重写hashCode()方法
为什么重写equals()方法时,要重写hashCode()方法
1. Object类中equals()方法源代码如下所示:
1 | /** |
由以上源代码知,
Object类中的equals()方法是直接使用==运算符来判断两个对象相等的。
- 引用类型变量使用
==时,比较的是引用类型变量指向的对象的内存地址- 基本类型使用
==时,比较值
Objcect类中的hashCode源代码如下:
1 | /** |
上面的注释中有说明如下几点:
- 对象的
hashCode值通常是根据对象的内存地址计算得来- 两个对象
equals()结果为true时,两个对象的hashCode值一定相等,不同对象的hashCode不等native标识此方法不是java语言实现
Object类中的toString()方法源代码如下:
1 | public String toString() { |
2. String类中equals()方法和hashCode()方法
String类中部分源代码如下所示:
1 |
|
从上面的源码中,我们不难发现String类已经重写了equals()方法和hashCode()方法。
String类重写的equals()方法判断流程如下:
- 使用
==来判断两个对象的内存地址是否相同,相同返回true;- 如果两个对象的内存地址不同,程序继续往下走,判断另一个对象是否是
String类型的;- 如果比较对象不是
String类型,直接返回false;- 如果是
String类型的,进行类型强转;- 比较两个
String的字符数组长度,如果长度不同,返回false;- 利用
while循环来逐位比较字符是否相等,直到循环结束,所有字符都相等,则返回true,否则返回false;
下面来看一下重写的hashCode()方法。
- 首先
String类中定义了一个int类型的变量hash用来缓存String对象的hash值;- 如果当前调用
hashCode()方法的String对象在常量池没有找到,并且该对象的length长度大于0,则继续往下走,否则返回0;即String类默认""字符串的hashCode()值为0;- 遍历字符数组,获取每一个字符的
ASCII码表对应的值 和之前的hash值相加,这样就保证了相同的字符串的hashCode()返回值相同,计算公式在注释里已经写出来了:**s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]**- 将计算出来的结果保存到
hash变量中,并返回该值;
这里为什么要乘以31呢?原因是为了性能,不仅仅指降低了计算速度,也降低了哈希冲突的概率。
哈希冲突:此处指不同的字符串生成了相同的
hashCode值。
31是一个奇素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算(低位补0)。使用素数的好处并不很明显,但是习惯上使用素数来计算散列结果。 31 有个很好的性能,即用移位和减法来代替乘法,可以得到更好的性能: 31 * i == (i << 5)- i, 现代的 VM 可以自动完成这种优化。这个公式可以很简单的推导出来。 —- 《Effective Java》
素数:质数又称素数,指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。
说了这么多,为什么重写equals()方法要重写hashCode()方法呢?
原因如下:
- 避免两个对象不同时出现相同的hashCode值
- 避免重写过equals()方法的对象使用equals()判断为相等时,返回不同的hashCode值
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 wshawk's blog!
