今天说说HashMap为什么要同时重写hashCode和equals方法 , 为什么不只重写hashCode或者只重写equals呢 ? 算是自己也写个笔记记录记录吧 !
首先是hashmap的put方法 , 这个put方法的过程一定是要保证hashmap的键值对的唯一性 、 键的唯一性 。
hashmap 初始化的时候是一个table 数组 , 默认是16大小 , 然后如果有了hash冲突每一个键值对的下方会有一个链表进行hash冲突的解决方式 , 这里不说为什么了 , 主要讲重写问题 。
假如只重写hashcode , 不重写equals :
put ("a" ,"123") ; ---> 存这个的时候调用key 的hashCode , 因为Object的hashcode返回的是内存地址 ,如果不重写hashcode , 那么同样的一个键值对 , 唯一性得不到保证 。
假设上方put ("a" ,"123") ; put成功 ,如果这个时候继续调用一次put ("a" ,"123") ;那么如果没有重写hashCode , 则此时两者都用的Object的hashcode去返回内存地址 , 两者hashcode比较肯定是不同的 ,原因在于不是同一个对象 。
则存储的情况是这样的 : 假设table 长度为 6
1 | 2 | 3 | 4 | 5 | 6 |
(a , 123) | (a,123) | ||||
所以不重写Object的hashcode ,那么键值对的唯一性无法保证 , 针对同一个key , 因为是不同对象 ,所以hashcode肯定是不同的。放的位置就会不同 。
那么这里介绍了一定要重写hashcode , 保证键值对的唯一性 。
下面说说为什么也要重写equals呢 , 首先说说object 的equals 是 比较是否为同一个对象 , 即内存地址是否一样 。
此时我还是 put ("a" ,"123") ; 接着我在put ("b" ,"123") ;两次put 元素 ,假设这两次put的时机刚好key的hash 命中了 ,即发生了hash冲突 , 那么按照hashmap的解决方式 , 用了链表进行冲突位置的连接 , 存储情况如下:
1 | 2 | 3 | 4 | 5 | 6 |
(a,123) | |||||
(b,123) |
那么两者都放在了1号位置 , 并且两者通过一个链表进行连接 , 即(a,123) 的下一个元素就是(b,123) ;这种情况我假如调用一次get方法 -》 get(a) ;
那么执行过程是 对 a 进行hashcode 的判断 ,知道是在1号位置 , 那么去1号位置 , 发现有两个元素,
一个是(a,123) 、一个是(b,123) , 那我应该怎么判断呢 ? 肯定是用equals 进行判断 。
这是重写的源码:
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Map)) return false; Map,?> m = (Map,?>) o; if (m.size() != size()) return false; try { Iterator> i = entrySet().iterator(); while (i.hasNext()) { Entry e = i.next(); K key = e.getKey(); V value = e.getValue(); if (value == null) { if (!(m.get(key)==null && m.containsKey(key))) return false; } else { if (!value.equals(m.get(key))) return false; } } } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { return false; } return true; }
不仅仅判断key是否相等 , 还要判断value也要相等 。
结论 : 通过重写的hashcode 直接定位到key 的位置 , 根据重写的equals 保证唯一性。