标题即结论,java中HashMap的key是对象,需要谨慎操作。
背景
最近遇到(其实是亲手写出的)一个小bug,用HashMap<Integer, Boolean>存储了一些东西,但是在get数据的时候,key用的是long类型,导致结果读不出来。
实验
用一个小实验说明一下
public class Test {
public static void main(String[] args) throws ParseException {
HashMap<Integer, Boolean> map = new HashMap<>();
map.put(188, true);
System.out.println(map.get(188));
System.out.println(map.get(188l));
}
}
输出结果为
true
null
分析
表面上看,是存储的时候存的是int类型,读的时候用long类型就读不到。
但这个说法不精准,HashMap里存的是对象,而不是基础类型。所以肯定是存的Integer类型的对象,用Long类型的对象去查找是找不到的。
这里其实涉及到了java基础类型的自动装箱,那么自动装箱是怎么做的呢?
扩展
上面的代码,188 和188l 值其实是一样的,为啥找不到呢?是因为java有对基础类型的自动装箱,188最后成为了Integer类型的对象,188l最后成为了Long类型的对象。具体是怎么做到的呢?把代码反编译一下就可以看到了
反编译命令: javap - c Test (这个Test指定的是class文件,-v显示的信息更多,但是对于这个分析来说-c足够了)
public static void main(java.lang.String[]) throws java.text.ParseException;
Code:
0: new #2 // class java/util/HashMap
3: dup
4: invokespecial #3 // Method java/util/HashMap."<init>":()V
7: astore_1
8: aload_1
9: sipush 188 //把188 压入程序栈
12: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 调用Integer.valueOf来把188自动装箱变成Integer对象
15: iconst_1
16: invokestatic #5 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
19: invokevirtual #6 // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
22: pop
23: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_1
27: sipush 188 // 这是第一个get(188),下面转成Integer对象,然后调用get方法
30: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
33: invokevirtual #8 // Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
36: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
39: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
42: aload_1
43: ldc2_w #10 // long 188l 这是第get(188l),下面转成Long对象,然后调用get方法
46: invokestatic #12 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
49: invokevirtual #8 // Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
52: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
55: return
通过上面的代码我们可以看到数字都被Integer.valueOf 和 Long.valueOf 转成了对象,这就是自动装箱的逻辑。
至于,为什么Integer的188和Long的188不能被判断为相等,但是两个Integer的188可以被判断为相等,我们可以去看HashMap的get方法实现,传进来的对象首先计算hash,去找对应的hash是否存在;在hash存在的情况下再去判断对应的key是否真正存在,这里判断是否存在先用的是对象的==,然后用的是equals方法,二者满足其一即可。
Integer(188)调用equals(Long(188))肯定是返回false的。
总结
一个小问题能扯这么多,给自己点赞👏