
浅谈JAVA中拆箱与装箱
一. 什么是装箱?什么是拆箱?
在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:
Integer i = new Integer(10); |
在栈中储存引用变量;该引用变量指向在堆中储存的对象i;
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i=10
这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
跟装箱对应,就是自动将包装器类型转换为基本数据类型:
Integer i = 10; //装箱
int n = i; //拆箱
*装箱:自动将基本数据类型转换成包装器类型
拆箱:包装器类型自动转换成基本数据类型
基本数据类型对应包装类:
Btye(btye 1个字节)、Short(short 2个字节)、Integer(int 4个字节)、Long(long 8个字节)、Character(char 2个字节)、Float(float 4个字节)、Double(double 8个字节);
二. 装箱和拆箱是如何实现的
以Interger类为例,下面看一段代码:
public class Main { public static void main(String[] args) { Integer i = 10; int n = i; } } |
通过反编译去看(原文作者通过反编译javap去看的,我们可以通过反编译工具JD-GUI或者XJAD去查看.class文件都可以根据个人习惯)不上图了(偷懒,哈哈)
从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。
其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。
因此可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
三、实例说明
1、
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
结果:
true
false
输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
我们来看看IntegerCache类的实现:
private static class IntegerCache {
static final int high;
static final Integer cache[];
static {
final int low = -128;
// high value may be configured by property
int h = 127;
if (integerCacheHighPropValue != null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
int i = Long.decode(integerCacheHighPropValue).intValue();
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。
缓冲机制
JDK API文档中对这个新的valueOf方法有明确的解释:
如果不需要新的 Integer 实例,则通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能 .
注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。
IntegerCache初始化后内存中就有Integer缓冲区cache[]了,-128~127区间的int值有其对应的的包装对象。这就是 valueOf 方法真正的优化方法:
实例2:
publicclass
ZhuangXaing
{
public static void main(String[] args){
Integer i=
newInteger(
12);
Integer j=12;
Integer k=Integer.valueOf(12);
Integer l= new Integer(232);
Integer m=232;
Integer n=232; Double q = 232.0;
System.out.println("use ==.......");
System.out.println(i==12);
System.out.println(i==j);
System.out.println(j==k);
System.out.println(l==232);
System.out.println(l==m);
System.out.println(m==n);
System.out.println("use equals.....");
System.out.println(m.equals(n));
System.out.println(m.equals(q));
}
}
输出结果:
use
==.......
true
false
true
true
false
false
use equals.....
true
false
Integer i= new Integer(12);
是指明了在堆内存中创建对象; Integer j=12;
是自动装箱,调用valueOf 方法,返回return IntegerCache.cache[12 + 128]
, 得到的是Integer 缓冲池中的对象。Integer k=Integer.valueOf(12);
与Integer j=12;
本质上相同,指向缓冲池中同一对象。包装对象与数值比较,自动拆箱。
而对于大于127 的数值,执行的都是return new Integer(i)
都在堆内存中,但是地址不同。
equals 方法比较的是数值大小:
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
可以看出比较的 obj 如果是 Integer 的实例,则比较拆箱后数值的是否相等。否则返回false。
实例3:
public class Main {
public static void main(String[] args) {
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
//false
//false
因为Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
那么这些包装器的缓存范围是多少了?
Integer (-128~127缓存)
Boolean: (全部缓存)
Byte: (全部缓存)
Character ( <=127 缓存)
Short (-128~127 缓存)
Long (-128~127 缓存)
Float (没有缓存)
Doulbe (没有缓存)
实例4:
public class Main {
public static void main(String[] args) {
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
结果:true true
Boolean的valueOf方法的具体实现:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:
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);
四、自我理解的Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;
第一种是在栈定义了引用变量指向堆里对象,在new 对象时不会去内存里查找是否有重复的数值,都会重新开辟一个新的堆内存;并在栈中有一个引用变量指向该对象。
2) 第二种会触发自动装箱的过程,第二种相当于Integer i=Integer.valueOf(XXX);如果该字面值没超出该包装器的缓存范围则会在缓冲区进行缓存,如果定义使用该方法一个相同字面值时会先进缓冲区查找是否有相同的值有就把引用变量指向这个已存在的对象;如果该方式定义的对象取值大于缓存范围就会在在堆内存里开辟空间;虽然都是在堆内存里但地址不同。
在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)
实例5:
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}
结果:
true
false
true
true
true
false
true
第一个和第二个输出结果没有什么疑问。第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
如果对上面的具体执行过程有疑问,可以尝试获取反编译的字节码内容进行查看。
如果有哪位朋友有补充的内容,如有写错的地方请多指教,欢迎下方留言,不胜感激。
****************************************************************************************************************