探索equals()和hashCode()方法#
在根类Object中,实现了equals()和hashCode()这两个方法,默认:
equals()是对两个对象的地址值进行的比较(即比较引用是否相同),用==实现。
hashCode():计算出对象实例的哈希码。根类Object的hashCode()方法的计算依赖于对象实例的内存地址,即内存地址由哈希函数生成一个int值,故每个Object对象的hashCode都是唯一的;当然,当对象所对应的类重写了hashCode()方法时,结果就截然不同了。之所以有hashCode方法,是因为在批量的对象比较中,hashCode要比equals来得快,很多集合都用到了hashCode,比如Hashtable。
- 两个obj,如果equals()相等,hashCode()一定相等。
- 两个obj,如果hashCode()相等,equals()不一定相等(Hash散列值有冲突的情况,虽然概率很低)。
在集合中,判断两个对象是否相等的规则是:
第一步,如果hashCode()相等,则查看第二步,否则不相等;
第二步,查看equals()是否相等,如果相等,则两obj相等,否则还是不相等。
为什么选择hashCode方法?
比如set集合存储数据的时候是怎样判断存进的数据是否已经存在。使用equals()方法呢,还是hashCode()方法。假如用equals(),那么存储一个元素就要跟已存在的所有元素比较一遍,比如已存入100个元素,那么存101个元素的时候,就要调用equals方法100次。
但如果用hashcode()方法的话,每存一个数据就调用一次hashCode()方法,得到一个hashCode值及存入的位置。如果该位置不存在数据那么就直接存入,否则调用一次equals()方法,不相同则存,相同不存。这样下来整个存储下来不需要调用几次equals方法,虽然多了一次hashCode方法,但相对于前面来讲效率高了不少。
为什么要重写equals方法?
因为Object的equals()方法默认是两个对象的引用的比较,意思就是指向同一内存则相等,否则不相等;如果你现在需要利用对象里面的值来判断是否相等,则重载equals()方法。记住:String,Double、Integer、Math这些类已经重写了equals()方法,比较的是对象的值。
改写equals时总是要改写hashCode
如果不这样做到话,就会违反Object.hashCode的通用约定:相等的对象必须具有相等的散列码hashCode。根据一个类的equals方法,两个截然不同的实例有可能在逻辑上是相等的,但是,根据Object类的hashCode方法,它们仅仅是两个对象,对象hashCode方法返回两个看起来是随机的整数,而不是根据第二个约定要求的那样,返回两个相等的整数。从而导致该类无法与所有基于散列值(hash)的集合类结合在一起正常运作,这样的集合类包括hashMap、HashSet和Hashtable。比如new一个对象,再new一个内容相等的对象,调用equals方法返回的true,但他们的hashCode值不同,将两个对象存入HashSet中,hashCode值不同,都可以存进去,这样set中包含两个相等的对象。因为是先检索hashCode值,相等的情况下才会去比较equals方法。
hashCode方法使用介绍
Hash表数据结构常识:
一、哈希表基于数组。
二、缺点:基于数组的,数组创建后难以扩展。某些哈希表被基本填满时,性能下降得非常严重。
三、没有一种简便的方法可以以任何一种顺序遍历表中数据项。
四、如果不需要有序遍历数据,并且可以提前预测数据量的大小,那么哈希表在速度和易用性方面是无与伦比的。
为什么HashCode对于对象是如此的重要(前面已经举了set的例子):
HashMap和Hashtable,虽然它们有很大的区别,如继承关系不同,对value的约束条件(是否允许null)不同,以及线程安全性等有着特定的区别,但从实现原理上来说,它们是一致的。所以,我们只以Hashtable来说明:
在java中,存取数据的性能,一般来说当然是首推数组,但是在数据量稍大的容器选择中,Hashtable将有比数组性能更高的查询速度。具体原因看下面的内容:
Hashtable在存储数据时,一般先将该对象的HashCode和0x7FFFFFFF做与操作,因为一个对象的HashCode可以为负数,这样操作后可以保证它为一个正整数。然后以Hashtable的长度取模,得到该对象在Hashtable中的索引。
index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
这个对象就会直接放在Hashtable的index位置,对于写入,这和数组一样,把一个对象放在其中的第index位置,但如果是查询,经过同样的算法,Hashtable可以直接从第index取得这个对象,而数组却要做循环比较。所以对于数据量稍大时,Hashtable的查询比数组具有更高的性能。
事实上一个设计比较好的Hashtable,一般来说会比较平均地分布每个元素,因为Hashtable的长度总是比实际元素的个数按一定比例进行自增(负载因子一般为0.75左右),这样大多数的索引位置只有一个对象,而很少的位置会有几个元素。但是,hash冲突很难完全避免,可以看hash。一般Hashtable中的每个位置存放的是一个链表,对于只有一个对象的位置,链表只有一个首节点(Entry),Entry的next为null,同时保存hashCode,key,value属性,如果有相同索引的对象进来则会进入链表的下一个节点。如果同一个索引中有多个对象,根据HashCode和key可以在该链表中找到一个和查询的key相匹配的对象(equals方法)。
对于一个对象,如果具有很多属性,把所有属性都参与散列,显然是一种笨拙的设计。因为对象的HashCode()方法被自动调用的很多,如果太多的对象参与了散列,那么需要的时间将会增加很多。可以挑选具有区分度的属性计算hash值,或者设立缓存,只要当参与散列的对象改变时才重新计算,否则调用缓存的hashCode,这可以从很大程度上提高性能。
默认的实现是将对象内存地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,但java语言并不能让程序员获取对象内存地址。
请记住:如果你想有效的使用HashMap,你就必须重写在其的hashCode()。
还有两条重写hashCode()的原则:
- 不必对每个不同的对象都产生一个唯一的hashCode,只要你的HashCode方法使get()能够得到put()放进去的内容就可以了。即“不为一原则”。
- 生成hashCode的算法尽量使hashCode的值分散一些, 不要很多hashCode都集中在一个范围内,这样有利于提高HashMap的性能。即“分散原则”。