装箱,拆箱可以让编译器来自动完成在基本类型和它们的包裹对象之间的转化工作,从而能够用一种更简单的方式,来避免同时存在两种类型所带来的一些麻烦。
在Java程序中,可以往一个容器类(无论是Collection还是Map)里直接放入一个对象;但是如果打算放入的是一个数字、字符或布尔值的话,就要先加入一个转换成对象的步骤。造成这种现象的原因是,在Java语言当中一直存在着两种非常不同的类型:
- “引用类型”(Reference Types),包括所有的类和接口。这些类型的数据被看作对象,所以可以用一个Object型的变量来保存。、
- “基本类型”(Primitive Types),包括:byte、short、int、long、float、double、char和boolean。这些类型的数据不是对象,因此也不能用Object型的变量来保存。
同时采用这样两种类型,可以得到一些性能方面的好处——因为基本类型的数据不是对象,所以创建得更快、占用的空间更少、收回它们占用的资源也更容易;但是,这样的做法同时也会造成一些编码方面的问题——例如,不能定义一个变量(或数组),让它既能保存基本类型的数据,又能保存引用类型的数据。
1) 自动封装(Autoboxing)
在J2SE 1.5之前,要使用以下语句才能将int包装为一个Integer对象:
Integer integer = new Integer(10);
在 J2SE 1.5之后提供了自动装箱的功能,就可以直接使用以下语句来打包基本数据类型:
Integer integer = 10;====》》隐式等于 new Integer(10)
在进行编译时,编译器再自动根据您写下的语句,判断是否进行自动装箱动作。在上例中integer参考的会是Integer类的实例。同样的动作可以适用于 boolean、byte、short、char、long、float、double等基本数据类型,分别会使用对应的打包类型(Wrapper Types)Boolean、Byte、Short、Character、Long、Float或Double。
程序看来简洁了许多,data1与data2在运行时就是Integer的实例,可以直接进行对象操作。执行的结果如下:
自动装箱运用的方法还可以如下:
3.14f会先被自动装箱为Float,然后指定给number
2) 自动拆箱(Auto-Unboxing)
从J2SE1.5开始可以自动装箱,也可以自动拆箱(unboxing),也就是将对象中的基本数据形态信息从对象中自动取出。例如下面这样写是可以的:
fooInteger引用至自动装箱为Integer的实例后,如果被指定给一个int类型的变量fooPrimitive,则会自动变为int类型再指定给fooPrimitive。在运算时,也可以进行自动装箱与拆箱。例如:
上例中会显示20与10,编译器会自动进行自动装箱与拆箱,也就是10会先被装箱,然后在i + 10时会先拆箱,进行加法运算;i++该行也是先拆箱再进行递增运算。再来看一个例子:
同样的boo原来是Boolean的实例,在进行AND运算时,会先将boo拆箱,再与false进行AND运算,结果会显示false。
1) 发生装箱(Autoboxing)的具体时机
主要有这么三种:
1) 把基本类型的数据赋给引用类型的变量时。例如把一个int型的数据赋给一个Integer型变量。
2) 把基本类型的数据传给引用类型的参数时。例如给一个定义成Object的参数传递一个boolean型的数据。
3)把基本类型的数据往引用类型上强制转化时。例如在一个long型的数据前面加上(Long)。
4)装箱的局限
装箱的机制有一个局限——只能把基本类型的数据转换为它相对应的类以及父类。
类似这样的代码是不能工作的,尽管int型的数据完全可以用一个Long对象来表示:
这是因为这段代码实际上相当于:
而Integer并不是Long的子类,所以这个转化无法进行。如果一定要进行这种操作,需要手工追加一次转型:
发生拆箱(Auto-Unboxing)的具体时机
则主要有这么七种:
1) 把包裹类对象赋给基本类型的变量时。例如把一个Integer型的数据赋给一个int型变量。
2) 把包裹类对象传给基本类型的参数时。例如给一个定义成boolean的参数传递一个Boolean型的数据。
3)把包裹类对象往基本类型上强制转化时。例如在一个Long型的数据前面加上(long)。
4)把包裹类对象当作运算符的操作数时。例如在两个Byte型的数据之间放上“+”号。
5)用包裹类对象来指定数组的大小时。当然,从语义上说,这个对象的类型必须是Byte、Short、Integer或Character。
6)把包裹类对象在switch语句里使用时。当然,从语义上说,这个对象的类型必须是Byte、Short、Integer或Character。
7)把Boolean对象在if/for/while/do-while语句中作为条件表达式使用时。
拆箱的局限
Auto-Unboxing的机制则有这样一个局限——只能把包裹类对象往它们对应的基本类型(以及容纳范围更广的类型)上转化。
类似这样的代码是不能工作的,尽管32并未超出byte所能表示的范围:
这是因为编译器并不认可同时进行Auto-Unboxing和强制向下转型的操作,所以这个转化无法进行。如果一定要进行这种操作,需要手工补充一次转型:
不过同时进行Auto-Unboxing和强制向上转型的操作是没有问题的,所以下面的代码工作得很正常:
其它不能自动转化的情况
除去强制类型转化时的限制之外,还有这样一些情况下不会发生Autoboxing/Auto-Unboxing:
1)基本类型的数组和包裹类数组之间不会自动转化。这样的代码完全不被编译器接受:
2)不能对着基本类型的表达式来调用包裹类里的方法。这样的申请会被编译器彻底拒绝:
null的转化问题
Java里的引用类型可以有一个特别的取值——“null”。试图对null进行Auto-Unboxing操作会导致一个“NullPointerException”。
例如这段代码就会在运行时抛出异常,尽管在编译期间会表现得非常正常:
这是因为这段代码实际上相当于:
而试图调用null的方法是一种不被虚拟机认可的行为。
归纳总结
借助Autoboxing/Auto-Unboxing机制,可以用一种更简单的方式,来解决同时存在两套类型系统而造成的一些不方便。不过,这种机制并没有解决所有的相关问题,有些工作还是需要靠手工操作来进行。另外,由于不恰当的使用这一机制会造成一些性能方面的负面影响,所以在使用的时候需要谨慎。