前言
这一篇主要想讲一讲HashMap在设计上的缺陷以及在使用的过程中留下的一些隐患。也是在实际项目中可能需要注意的一些地方。比如说我下面要介绍的一个containsKey方法,以及List里面其实有一个toArray[]方法返回的是一个Object[]数组的,其实都不是很好用的一种设计,在泛型里有点不伦不类的感觉。
项目背景
事情的起因是在项目中因为漏改
可能引起的一个故障想起的。这里大概介绍一下整个过程:有这样一种场景,定义了一个HashMap<
Integer,Long>类型的变量,Key是Integer类型的,后来因为某些原因把这个变量改成了HashMap<
Long,Long>,这里注意一下这个Key,
由Integer变成Long类型,讲到这里还没有什么问题。问题出在哪里,代码里面有一个逻辑是
需要判断里面的Key是否存在
(也就是会调用containsKey(Object key)这个方法),但是这里因为某些原因传的值仍然是
Integer类型的。问题就出来了,这样会发现从HashMap是拿不到value的,结果就是containsKey()这样一个方法后返回的是false。接下来会用一个实例来还原这样我上面所说的场景:
实例
- public static void main(String[] args) {
- Map<Long,Long> map = new HashMap<Long,Long>();
- map.put(new Long(1), new Long(1));//这里的key是Long类型
- Integer key = new Integer(1);
- boolean result = map.containsKey(key);//这里的key是Integer类型
- System.out.println(result);
- }
先看一看输出结果,没错,是false,没有任何疑问,这里可以看看HashMap是如何查找value的:HashMap会先比较key的hashCode,然后会直接比较这个key值是否相等(== || equals),这里可以参考我另一篇文章《HashMap源码分析》。下面我把HashMap获取Entry的方法给贴出来,重点看一下里面查找Key的方式。
- final Entry<K,V> getEntry(Object key) {
- int hash = (key == null) ? 0 : hash(key.hashCode());
- for (Entry<K,V> e = table[indexFor(hash, table.length)];
- e != null;
- e = e.next) {
- Object k;
- if (e.hash == hash &&
- ((k = e.key) == key || (key != null && key.equals(k))))
- return e;
- }
- return null;
- }
接着我把Integer和Long的equals方法贴出来,看看这两个类对于equals方法的实现,这样就可以比较清晰的知道为什么在HashMap中containsKey如果里面本来存的key是Integer类型,而调用containsKey传的值是Long类型后得到的结果是false。
Integer的equals方法
- public boolean equals(Object obj) {
- if (obj instanceof Integer) {
- return value == ((Integer)obj).intValue();
- }
- return false;
- }
Long的equals方法
- public boolean equals(Object obj) {
- if (obj instanceof Long) {
- return value == ((Long)obj).longValue();
- }
- return false;
- }
吐槽
讲到这里估计有些人有疑问了。上面讲的有什么问题吗?这些大家都知道啊。这不是最基本的知识点吗?等等,好戏才刚刚开始。这里注意看一下HashMap中containsKey这个方法(下面是源代码):
- public boolean containsKey(Object key) {
- return getEntry(key) != null;
- }
- public boolean containsKey(K key) {
- return getEntry(key) != null;
- }