再谈HashMap-由一个实际问题引发的对HashMap设计吐嘈

前言

       这一篇主要想讲一讲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 map = new HashMap();
        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 getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry 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;
    }
        从上面Integer和Long的equals方法可以得出如果比较的两个值都是Integer类型或者Long类型,只要它们的值( intValue/longValue相等),那么equalsr后的结果是true,但是Integer类型和Long类型的两个对象进行比较,即使值(intValue/longValue)相同,equals后是false。这就可以解释上面的一个结论为什么是false了。

吐槽

       讲到这里估计有些人有疑问了。上面讲的有什么问题吗?这些大家都知道啊。这不是最基本的知识点吗?等等,好戏才刚刚开始。这里注意看一下HashMap中containsKey这个方法(下面是源代码):
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }
       传进去的参数是 Object类型,这个方法的设定就有点不伦不类,因为HashMap使用了泛型,按照我们一般人的理解,应该会是这样(下面是我改进的方法):
    public boolean containsKey(K key) {
        return getEntry(key) != null;
    }
        我所做的改动仅仅是把里面的 Object换成了泛型K。这样的设计有什么好处:再回到上面的那个实例,如果containsKey方法值的是泛型,那么在传值的时候就会约束成Long类型,是不允许传Integer值,这样在编译的时候就会抛错,而不会给整个逻辑留下一个不可预知的隐患,也符合越早发现问题越好的一个设计原则:能把问题留在编译期就不要把问题带到运行期甚至不可发现。这也是为什么要用泛型进行约束,除了代码规范外,另外一个好处就是可以消除因为编码引入的bug。

总结

       上面我从一个实例入手来讲用HashMap中遇到的一些坑,反映出HashMap设计不完美之处。这样设计其实也有一些无奈之举,这也是因为HashMap的泛型是JDK 1.5引入的,为了让之前的版本得到比较好的兼容才保留了之前原有的接口。最后讲到一个设计原则就是能把问题留在编译器就不要把问题留在运行期,越早发现越好。再讲讲最上面那个项目的事情,幸好这个问题发现了,不然后果将会非常严重,心有余悸。




       

你可能感兴趣的:(Java,API)