一条语句引发的思考:装箱和拆箱,空指针的类型转换

最近做CodeReview,发现了一位已经离职同事写的代码,大致如下,令人费解。(其中map是Map<String, Object>)

1 try {
2             
3             int count = (Integer) map.get("count");
4             
5         } catch (NullPointerException e) {
6             // do something.
7         }

先不说这样写好不好,引起我兴趣的是,map这个变量,已经在上文判断是否为空指针了,map.get("count")也不会抛出空指针异常。为什么这里还要判断?

经过一番搜索学习,想要理解上面的语句,那么你需要了解以下知识点。

1. null 与 对象的转换

null既不是对象也不是一种类型,它仅是一种特殊的值,你可以将其赋予任何引用类型,你也可以将null转换成任何类型。例如下面的代码,是可以运行的。

关于null的知识点,可以参考 Java中有关Null的9件事

Integer a = (Integer) null;
Double b = (Double) null;
Boolean c = (Boolean) null;

2.装箱和拆箱

Java为每种基本数据类型都提供了对应的包装器类型,自jdk 1.5之后提供了自动装箱(autoboxing)和拆箱(unboxing).


上面这个就没什么好讲的了。

所谓装箱和拆箱,就是指基本类型和包装器类型的互相转换。

装箱:基本类型->包装器类型;拆箱:包装器类型->基本类型。

1 Integer integer = 1; //装箱
2 int i = integer; //拆箱

3. 装箱和拆箱的实现

对上面的代码进行编译,查看字节码,如图所示:

装箱的时候,使用静态的valueOf()方法;拆箱的时候,使用非静态的intValue()方法。

经过测试,上述的所有类型,装箱都会调用静态的valueOf()方法,而拆箱使用非静态xxxValue()方法。

4.一个小陷阱

来看下面的代码,会是输出什么呢?

1 Integer a = 1;
2 Integer b = 1;
3 Integer c = 200;
4 Integer d = 200;
5 System.out.println(a == b);
6 System.out.println(c == d);

Integer是引用类型,引用类型是要看引用的地址的,很明显这四个都不是同个对象,都打印false.

然而……

这里就涉及到一个缓存的问题。查看Integer的valueOf()代码,可以看到,在某个范围内,会从缓存取值,这样取出来的,就是同一个对象了。

 IntegerCache的范围一般是[-128,127]。(是否可以修改,待确认)

经检查

1)Integer, Byte, Long, Short都是从[-128, 127];Character是[0,127];Boolean是FALSE or TRUE.

2)Double和Float没有缓存的概念

3)通过直接创建的对象,不会从缓存中获取。

1 Integer a = new Integer(1);
2 Integer b = new Integer(1);
3 System.out.println(a == b);

打印false;

5. 包装器的符号运算(以Integer为例)

1)==,两边都是Integer,比较内存地址;存在一边是基本类型,比较数值大小

2)+,转换成基本类型后相加

这个就不证明了。 有兴趣按照上面的方式检查(javap -c xxxx)

6. 问题分析

回到最开始的问题,为什么会抛出空指针异常?

1 int count = (Integer) map.get("count");

1)map获得的值,有可能为null.

2)null可以转换成所有类型,于是得到一个声明为Integer类型的变量,该变量实际上指向为空

3)Integer转成int,发生拆箱,调用非静态的intValue()方法,而变量实际上为空,那么就会抛出空指针异常

 

posted @ 2016-10-29 08:17  kingsleylam  阅读(1270)  评论(0编辑  收藏