Java 装箱与拆箱
以前在用包装类的时候从来都不会关心里面的具体实现,只是感觉从基本类型一下子就到了类对象真神奇。今天参考了海子的博客[1],决定对包装类进行下系统的认识。
一、什么是包装类?包装类和基本类型相互赋值会有什么动作发生?
众所周知,Java有8种基本类型,为了面向对象的需要为这八种类型各自都设计对应的包装类型。比如int 对应的 Integer。
如果要生成一个值为20的Integer有以下两种写法:
Integer i=new Integer(20); Integer i2=20;
第一种是普通的新建对象,第二种采用的是直接赋值。这两种写法会产生不同的动作,稍后再说。
这里需要关注的是第二种,用赋值号“=”将根据int值创建对应的Integer对象,这就是装箱。
那么什么是拆箱呢?
int i3=i2;
用"="将Integer对象复制给int类型变量,就是拆箱。
二、拆箱与装箱是怎么实现的
这里面用了个例子
public class Main { public static void main(String[] args) { Integer integer=10; int i=integer; } }
通过反编译后得到下面的内容
从反编译的结果可以看出Integer的装箱用了Integer.valueOf 而拆箱用了Integer.intValue。
因此可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
之前一直没怎么注意这个细节,在将字符串转换成int型的时候,preseInt和valueOf混用,以后得注意了要用parseInt.
三、包装类的分析
1>包装类可以分为三种
1、包装类中Character,Byte,Short ,Integer,Long 是类似的,他们有一个很大的共同点就是都有对应的Cache。其大小和范围如下:
类名 | 大小 | 范围 |
Byte | 128 | 0~127 |
Character | 256 | -128~127 |
Short | 256 | -128~127 |
Integer | 256 | -128~127 |
Long | 256 | -128~127 |
这些Cache都在static块中实现的,在包装类被加载的时候即被创建,这些被创建的对象保存在堆中,引用保存在常量池中,当通过装箱新建一个包装类时,会进行判断假如在范围内的直接返回该数值对应的包装类的对象,否则新建。
类如:
Character c1='A'; Character c2='A'; Character c3=new Character('A'); System.out.println(c1==c2); System.out.println(c2==c3);
输出是true false 即通过装箱得到的包装类对象会执行上述动作,而通过new创建的对象不会。
Character c1=128; Character c2=128; System.out.println(c1==c2);
这里输出的是false 即在范围之外的直接新建对象,并返回对象引用。
2、Float,Double这两个包装类没有对应的Cache,那么每次装箱都会新建一个对象
Double d1=128.0; Double d2=128.0; System.out.println(d1==d2);
输出是false ,Float类型也是一样
3、Boolean
Boolean默认创建了两个Boolean对象TRUE和FALSE。
public static final Boolean TRUE = new Boolean(true); /** * The <code>Boolean</code> object corresponding to the primitive * value <code>false</code>. */ public static final Boolean FALSE = new Boolean(false); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
这样只要逻辑上相同,则对应的包装类对象是相同的。
2>Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:
1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;
2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。
3>编译器的处理
这里引用海子的例子并稍加修改,有兴趣的朋友直接点击下面的链接前往原文。
public static void main(String[] args) { int i=1; Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; Long h = 2L; Long k=new Long(2L); System.out.println(i==a);//1 System.out.println(c==d);//2 System.out.println(e==f);//3 System.out.println(c==(a+b));//4 System.out.println(c.equals(a+b));//5 System.out.println(g==(a+b));//6 System.out.println(g.equals(a+b));//7 System.out.println(g.equals(a+h));//8 System.out.println(k.equals(a+h));//9 }
当"=="两边都是包装类引用时,会直接判断是不是同一对象,即地址是否相同;假如一边是基本类型,一边是包装类对象时,那么会使用xxxValue进行拆箱,对数值进行比较,;当一边出现运算符时,同样也是进行拆箱,对数值进行比较。
当使用的是equals,则会先对表达式进行运算,然后进行装箱,(不会进行类型转换),判断是否同一对象。
上面的输出是:
true //1 true //2 false //3 true //4 true //5 true //6 false //7 true //8 false //9
第一个,一边是基本类型一边是包装对象,进行拆箱进行比较数值 true
第二个,第三个参考上面,也好理解
第四个 右边有运算符,进行拆箱比较数值 true
第五个用的是equals ,现将a和b拆箱进行数值相加得到3,然后将3装箱,与c进行比较,由于在范围内所以是true;
第六个 两边都进行拆箱,进行数值比较
第七个 一边是Long 一边是Integer 不等
第八个 右边在进行算术运算时将int转换成long,最后装箱 都是long ,且在范围内。
第九个 k是直接创建的对象,肯定和其他的不是同一对象。