equals和hashCode都是Object类的方法。意味着我们的任何对象都具有这两个方法,并可以重写它们。
equals方法用来判断两个对象是否 “ 相等 ” 。
Object类上是将两个对象的地址进行比对,但是这样的规则,并不符合实际生活上的我们是怎么判断两者是否相等的。因此你需要进行重写,写自己的判断规则。
如:我们需要比较两个学生的年龄是否相等,Object类的equals则是比较这两个学生是否是同一个人。我们就只对其年龄进行比较。
hashCode方法返回此对象的散列值,而散列值的生成方式也可以自己进行重写。Object类的hashCode是返回对象的内存地址。
以上两个方法之间的联系是:若两个对象 “ 相等 ” ,那么它们返回的散列值也必须相同。(这是需要重写hashCode的第一个原因)
反之,则不然,若散列值相同,也不能直接判断两个对象 “ 相等 ”,还需经过equals方法的验证。
那么我们为什么需要重写hashcode方法呢?
与hashCode有关的一定有散列操作,我们随便举一个栗子:HashMap。
HashMap的存储结构是:数组+链表、红黑树,而其中存放的数据的结构是:<键,值>,
图示存储结构(取自网络)
在调用HashMap的put方法添加数据Entry时,会调用键对象的hashCode方法获取散列值,接着我们根据该值调用hash(Object key)方法获取新值。
//扰动函数,为了使散列效果好,减少碰撞冲突 static final int hash(Object key) { int h; return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16; }
通过hash(Object key)的返回值,再依据HashMap的当前容量长度(即数组长度)做位运算与的操作(tab[i = (n - 1) & hash])),获取Entry在数组中的存放位置。
补充:
若该位置上没有数据,即直接存放即可。
若该位置上存在值(即hash冲突),则利用头插法 or 尾插法插入其中,形成链表。
若该位置上的链表长度大于等于8,链表即可转换为红黑树,提高查找效率。另外,当处于红黑树结构的链表长度小于6又会重新转换成链表。(为什么不直接小于8就将红黑树转换为链表,答:转换结构也是比较耗资源的)
就是,当你需要使用散列操作(一般来说是为了更高的效率),你需要重写hashCode,因为默认的hashCode的返回值是对象在内存中的地址。这样的结果就是每一个对象的散列值都是独一无二的(即使其内容一致),那么在HashMap这种集合中,你就无法通过构造内容一致的键去获取map中的值。
以下图示HashSet添加数据的流程(取自网络)