什么是自动装箱和拆箱
自动装箱和拆箱在编译器中实现。
从原始数据类型(字节,短整型,长整型,浮点型,双精度型,字符型和布尔型)到对应包装器对象(字节,整数,长整数,浮点型,双精度,字符和布尔)的自动换行被称为自动装箱(AutoBoxing)。
反向,从包装器对象到其对应的原始数据类型,被称为取消装箱。
实现
装箱主要调用xx.valueOf(),比如Integer.valueOf(int);
自动拆箱,调用类似intValue(),doubleValue(), 等方法。
Integer n = 2; // Boxing
int a = n; // Unboxing
反编译后为
Compiled from "AutoBox2.java"
public class AutoBox2 {
public AutoBox2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 8
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3 // Method java/lang/Integer.intValue:()I
10: istore_2
11: return
}
自动装箱调用Integer.valueOf()获取包装后的对象,自动拆箱调用Integer.intValue()获取对应基础值。除了Integer,其余基础类型都有对应装箱和拆箱函数。
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
实例
- 赋值自动装箱
public static void main(String[] args) {
Integer a = 1;
}
Integer a = 1;
对于java编译器会将该语句编译为Integer a = Integer.valueOf(1);完成自动装箱。
与Integer i = new Integer(1)区别:该方法不会触发自动装箱。
- 拆箱可能存在NullPointerException异常
Integer i = null;
int j = i;
上面代码反编译如下
public static void main(String[] args)
{
Integer i = null;
int j = i.intValue();
}
i对象没有进行初始化或者为Null,在进行拆箱时,在null上执行intValue();会抛出触发NullPointerException错误。
- 缓存对象
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
if (i1 == i2) {
System.out.println("i1 == i2");
}
else {
System.out.println("i1 != i2");
}
}
运行结果:i1 == i2
如果修改 i1,i2值为200,运行结果为i1 != i2。
反编译为
public static void main(String[] args)
{
Integer i1 = Integer.valueOf(200);
Integer i2 = Integer.valueOf(200);
if (i1 == i2) {
System.out.println("i1 == i2");
} else {
System.out.println("i1 != i2");
}
}
在赋值是调用Integer.valueOf()进行自动装箱,不过Integer.valueOf()内部,存在缓存对象。
Integer.valueOf()的具体实现:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
调用Integer.valueOf()方法,对-128到127的Integer对象进行缓存。如果参数在这个范围,直接返回缓存对象,如果不在新建Integer对象。使用"==",比较对象是,比对的是对象的地址;当参数在-128到127之间,返回缓存中对象,地址相同;如果不在这个范围,新建对象,两个对象内存地址不会相同。
对于Boolean、Byte、Character、Short、Integer、Long六种基本类型,
对于一个字节以内的值-128到127(Boolean只有true和false)都实现了缓存机制。
不在此范围的数才在对应的valueOf()方法中new出一个新的对象。
但是对于Double和Float类型的浮点数据,在-128到127之间除了256个整数外还有无数的小数,
故java中没有实现Double和Float中一些数的缓存。
所以,对于Double和Float的自动装箱,都是new出新的对象
对象进行判断相同时,使用equals,不是使用"=="。
GC压力
public static void main(String[] args) {
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
}
反编译为
public static void main(String[] args)
{
Integer sum = Integer.valueOf(0);
for (int i = 1000; i < 5000; i++) {
sum = Integer.valueOf(sum.intValue() + i);
}
}
sum+=i,+操作不适用Integer对象,sum首先拆箱操作,进行数值相加,累加完成后,在进行自动装箱操作转化为Integer对象;整个循环中会创建将近4000个无用的Integer对象。
使用“==”进行比较时:
- 当基本类型包装类与基本类型值进行==运算时,包装类会自动拆箱。即比较的是基本类型值。
- 当两边都是包装类型,在==运算时不会触发拆箱操作;所以比较的是引用地址
- 在一侧存在表达式,会触发拆箱操作,运算后的结果为基本类型;然后包装类型与基本类型进行==运算,触发拆箱操作
网友评论