前言
上一篇文章简单分析了equals()与==的关系,本文我们再来看看equals()与hashcode()的关系。hashcode的使用还是有很多坑的,一起看看吧~
本文主要有以下几点来分析:
- hashCode使用中产生的问题
- equals/hashcode的渊源
- 产生问题的原因
- 正确的使用姿势
hashCode使用中产生的问题
注:HashSet是一个无序、不可重复的集合,我们做一个小测试运行如下代码:
1 |
|
由于HashSet是不可重复的集合,所以输出的结果中set1和set2中都应该只有一个元素,那么执行结果是什么呢?如下
1
Person{age='1'}
Person{age='1'}
好吧,又双叒叕和我想象的不一样,set1不重复,set2明显发生了重复现象,这是为什么呢?
这是因为equals、hashCode使用不规范导致的,问题且放在这,我们先看看equals和hashCode的关系
equals/hashcode的渊源
同为Object类中的方法
public boolean equals(Object obj)
public int hashCode()
- equals(): 用来判断两个对象是否相同,再Object类中是通过判断对象间的内存地址来决定是否相同
- hashCode(): 获取哈希码,也称为散列码,返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
由于同为Object类中的方法,所以基本上所有Java类都会继承这两个方法,所以通过阅读hashCode方法的注释发现了:
概括为以下几点:
- 该方法返回对象的哈希码,支持该方法是为哈希表提供一些优点,例如,HashMap 提供的哈希表。
- 同一个对象未发生改变时多次调用hashCode()返回值必须相同,
- 两个对象equals不相等,那么两对象的hashCode()返回必定不同(此处可用来提高哈希表性能)
- 两个对象的hashCode()返回值相同,两对象不一定相同,还需要通过equals()再次判断
- 当equals方法被重写时,通常有必要重写 hashCode 方法
通过第1点其实可以看出,hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置,当对象不会用来创建像hashMap、hashSet等散列表时,hashCode()实际上用不上。
产生问题的原因
了解了两者的关系,我们在回过头来看看产生问题的原因
分析原因前需要了解哈希表的底层实现,hashCode在哈希表中充当的作用:
举一个栗子说明下:
-
假设内存中有0 1 2 3 4 5 6 7 8这8个位置,如果我有个字段叫做ID,那么我要把这个字段存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到8个位置中去挨个查找
-
使用HashCode则效率会快很多,把ID的HashCode%8,然后把ID存放在取得余数的那个位置,然后每次查找该类的时候都可以通过ID的HashCode%8求余数直接找到存放的位置了
-
如果ID的HashCode%8算出来的位置上本身已经有数据了怎么办?这就取决于算法的实现了,比如ThreadLocal中的做法就是从算出来的位置向后查找第一个为空的位置,放置数据;HashMap的做法就是通过链式结构连起来。反正,只要保证放的时候和取的时候的算法一致就行了。
-
如果ID的HashCode%8相等怎么办(这种对应的是第三点说的链式结构的场景)?这时候就需要定义equals了。先通过HashCode%8来判断类在哪一个位置,再通过equals来在这个位置上寻找需要的类。对比两个类的时候也差不多,先通过HashCode比较,假如HashCode相等再判断equals。如果两个类的HashCode都不相同,那么这两个类必定是不同的。
其实在HashSet就是采用的这种存储和获取方式,通过HashCode和equals组合的方式来保证集合无重复。也说明了HashCode()在散列表中是发挥作用的
ok,我们分析下最开始的代码,找一下输出结果重复的原因(代码片段):
1 | HashSet set1 = new HashSet(); |
set1.add(“1”);:set1集合为空,找到hashCode对应在哈希表中的存储区,直接存入字符串1
set1.add(“1”);:首先判断该字符串1的hashCode值对应哈希表中所在的存储区域是否有相同的hashCode,此处调用String类中的hashCode()
,显然两次返回了相同的hashCode,接着进行equals()方法的比较,此处调用String类中的equals()
,由于两个字符串指向的常量池中的同一个字符串1,所以两个String对象相同,字符串1重复,不进行存储。
set2.add(p1);:set2集合为空,找到对象p1的hashCode对应在哈希表中的存储区,直接存入对象p1
set2.add(p2);:首先判断该对象p2的hashCode值对应哈希表中所在的存储区域是否有相同的hashCode,Person中未重写hashCode()此处调用Object类中的hashCode()
,所以jdk使用默认Object的hashCode方法,返回内存地址转换后的整数,因为p1、p2为不同对象,地址值不同,所以这里不存在与p2相同hashCode值的对象,直接存入对象p2
看到这里已经知道Set集合中出现重复的原因了。都是因为hashCode、equals的不规范使用。
正确的使用姿势
从Jdk源码的注释中可以看出,hashCode() 在散列表中才会发挥作用,当对象无需创建像HashMap、HashSet等集合时,可以不用重写hashCode方法,但是如果有使用到对象的哈希集合等操作时,必须重写hashCode()和equals()。
修改最初的代码如下
1 |
|
重写了equals和hashCode方法之后,执行结果就恢复正常了:
1
Person{age='1'}
总结
- hashCode主要用于提升查询效率提高哈希表性能,来确定在散列结构中对象的存储地址
- 重写equals()必须重写hashCode()
- 哈希存储结构中,添加元素重复性校验的标准就是先检查hashCode值,后判断equals()
- 两个对象equals()相等,hashcode()必定相等
- 两个对象hashcode()不等,equals()必定也不等
- 两个对象hashcode()相等,对象不一定相等,需要通过equals()进一步判断。
参考和感谢
哈希存储结构中添加元素的逻辑:https://blog.csdn.net/lijiecao0226/article/details/24609559
hashcode详解:https://www.cnblogs.com/whgk/p/6071617.html