equals相等,HashCode方法却有可能不等(超详细)
今天继续研究hashcode与equals方法。
Q:hashCode方法的作用是什么?
A:在说hashcode方法的作用之前,我先说一下hashcode的特性。
1.hashcode特性体现主要在它的查找快捷性,在Set和Map这种使用哈希表结构存储数据的集合中。HashCode方法的就大大体现了它的价值,主要用于在这些集合中确定对象在整个哈希表中存储的区域。
2.如果两个对象相同,则着两个对象的equals方法返回的值一定为true,两个对象的HashCode方法返回的值也一定相同。
3.如果两个对象返回的HashCode的值相同,但不能够说明这两个对象的equals方法返回的值就一定为true,只能说明这两个对象在存储在哈希表中的一个桶中。
4.如果一个对象equals方法被重写,那么该对象的HashCode方法也应该被重写(具体原因下面再介绍)
说了这么多,接下来说hashcode的作用,在java中集合主要有三种,分别是List、Set、Map,其中Set和Map中都是用了哈希表结构,在Set中它是不能存储相同元素的,要说HashCode方法的作用先得说Set和Map中存储一个元素的过程,由于Set是不能够存储相同元素,则每次给set中添加元素的时候第一步先判断集合中是否存在该元素,如果存在则舍弃不进行存储,如果不存在则进行存储。在java中判断两个对象是否相等一般都使用equals方法进行判断。比如现在Set中已经存在1000个结构复杂的对象,再添加第1001个对象时,它会在集合中进行对比,看是否已经存在,如果存在则舍弃,结束添加操作。那如果不存在,则它会将要添加的对象与集合中1000个对象进行equals操作判断相等,最后再进行添加,那么这样程序的效率就会很低。于是java采用了哈希表的原理。哈希算法也叫散列算法,是将数据依据特定算法直接指定到一个地址上来。1.如果该物理地址上没有元素存在,则直接将该数据存储在该地址上,不用再进行任何比较了。2.如果该地址上存在元素,则进行比较如果相同则舍弃,不再进行存储与比较了。3.如果不相等的话则发生了hash冲突的情况,在set和map中的处理方式是在该位置上生成一根链表将产生的hash冲突并且equals不相同的对象,挂在这根链表上,则每次添加元素时就不用和整个集合中的元素进行比较,而只需要使用HashCode定位到该数据应该存储的地址上,然后与该地址上的对象进行比较即可。 采用哈希表以及HashCode的这种方法,会大大提高类似于Set和Map这种存储方式的效率。所以HashCode在上面的过程中主要扮演了寻找每个对象在集合中具体存储区域的功能,每个对象都可以计算出他们的hash码,按hash码进行分组,每个分组对应着一个存储区域,根据一个对象的hash码就可以确定该对象的存储区域,这样就大大减少了查询匹配元素的数量,大大提高了效率。
Q:为什么重写equals一定也要重写HashCode方法?
A:在java中equals方法用于判断两个对象是否相等,而HashCode方法在java中主要由于哈希算法中的寻域的功能(也就是寻找数据应该存储的区域的)。在类似于set和map集合的结构中,java为了提高在集合中查询匹配元素的效率问题,引入了哈希算法,通过某种算法及我们的HashCode方法得到对象的hash码,再通过hash码推算出数据应该存储的位置。然后再进行equals操作进行匹配,减少了比较次数,提高了效率。在集合做了优化之后进行判断元素相等的过程是这样的,首先判断两个对象的HashCode方法返回的值是否相等,如果相等然后再判断两个对象的equals方法,如果HashCode方法返回的值不相等,则直接会认为两个对象不相等,不进行equals方法的判断。有这样一个场景有两个Student对象,equals方法认为如果两个对象的学号相同则认为这两个对象相同。可是如果没有重写HashCode方法只重写了equals方法,此刻并不能实现我们的要求,它首先会判断HashCode方法返回的值是否相等,由于我们没有重写HashCode方法,此时返回的值是不同的,因此不会去判断我们重写的equals方法。而如果重写HashCode方法不重写equals方法也是同样的效果,不重写equals方法实际是调用Object方法中的equals方法,判断的是两个对象的堆内地址。而我们重写的HashCode方法认为相等的两个对象在equals方法处并不相等。因此重写equals方法时一定也要重写HashCode方法,重写HashCOde方法时也应该重写equals方法。
Q:为什么equals方法不相等而HashCode方法返回的值却有可能相同呢?
A:HashCode方法实际上是通过一种算法得到一个对象的hash码,这个hash码是用来确定该对象在哈希表中具体的存储区域的。返回的hash码是int类型的所以它的数值范围为[-2147483648-+2147483647]之间的,而超过这个范围,实际会产生溢出,溢出之后的值实际在计算机中存的也是这个范围的。比如最大值2147483647+1之后并不是在计算机中不存储了,它实际在计算机中存储的是-2147483648。在java中对象可以有很多很多通过new关键字来产生。而hash码也是通过特定算法得到的,所以很难或者说几乎没有什么算法在这个范围内在这个情况下不会不产生相同的hash码的。也就是说在上述情况下肯定是会发生哈希碰撞的,因此不同对象可能有相同的HashCode的返回值。也有人说Object方法中的HashCode方法是通过内存地址得来的,是唯一的。可是HashCode方法是共有的,也就意味着它是可以被程序员重写的。因此不同环境下实现HashCode的算法可能不同。因此equals方法返回结果不相等,而HashCode方法返回的值却有可能相同!