驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
通过hashcode的源码来理解重写equals和hashcode
/  

通过hashcode的源码来理解重写equals和hashcode

开篇

这篇文章主要通过HashMap#put的源码来聊聊为什么当将自定义的对象需要使用到Hash类型的数据结构的时候,需要重写hashcodeequals

这是在某个大厂面试的时候,感觉自己表达不够清楚,这里再总结下,希望对大家有所帮助。

额外补充一句,程序猿要晋级到工程师抑或架构师,该从底层去探究的东西还是要耐心结合源码进行分析,不是仅仅看别人的总结和理论知识点就可以了,需要自己结合源码进行理解、巩固、实践。

为什么

对于散列结构的数据类型,比如HashMap或者HashSet这些数据类型。若我们从业务上判定对象是表示的同一个事物,那么需要重写equals和hashcode,来保证在内存中不同的对象在业务上是一致的。

上面这话有些绕。业务上相同的但是代码层面不相同的对象,当需要hash结构进行保存的时候,就需要重写hashcode和equals方法了。

默认情况下

  • equals是继承自Object父类的,其中通过==进行对比
  • hashcode也是继承自Object父类的,通过native方法进行hashcode计算。
public boolean equals(Object obj) {
        return (this == obj);
    }

	public native int hashCode();

Hashmap源码

以下是JDK8的hashmap的源码,对此我想吐槽下:写的真是精炼,能一行写的,绝不两行写,最开始看的时候,变量哪里赋值都看了半天,实际项目编码中个人还是不推荐这种精炼的写法的,代码写出来是给大部分人读的以及后续维护的,所以性能一致或者差不多的情况下 "可读性才是最重要的"

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&   // 关键1
                ((k = p.key) == key || (key != null && key.equals(k)))) //关键2
                e = p;
          	    //省略。。。
            }}}}

上述代码是JDK8关于HashMapput的底层源码,有部分省略的地方,请注意查看"关键1"和
"关键2"

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k)))) // 关键1
                e = p;

其中p表示的是数组中根据hashcode运算后找出的某个具体的数组值。

其中eexsiting的缩写,表示已经存在的,该值需要在最后做判定后插入。


说回关键1处的代码:源码编写者应该是有意的通过 && 进行分割。关于&&&的区别,我们下篇文章再谈,很有意思的一个知识点。

  • 上面的判定是p.hash == hash这个很明显是判定hash值。重写hashcode的原因在这里。
  • 下面的判定是(k = p.key) == key (key != null && key.equals(k)),要求满足其中1个就行了。
    • 前者通过==直接判定内存地址,这个判定肯定同一个对象。因为==我们是没有办法重写的。
    • 后者是判定的equals,这个判定是标题提到的需要重写的equals方法。重写eqauls的原因在这里。

关键代码2处的代码也是类似的思路,请读者自行理解,不再赘述。

总结

hashcode被重写是因为保证同一个业务对象在数组寻址时,落到同一个节点和对链表第一个节点进行判定。

eqauls被重写是因为需要在链表中寻找该对象是否已经存在,来判定替换或者插入。

骐骥一跃,不能十步。驽马十驾,功在不舍。