先有个大概了解,自动装箱和自动拆箱是java的一个语法糖(让我想到了C#中get和set的语法糖),是只针对基本数据类型和它对应的包装器类型的。
一、自动装箱
一般情况下,创建一个类的对象时,需要通过new关键字来实例化,比如:
Object obj = new Object();
但是对于包装器类型,除了用new创建外,还可以这样:
Integer a = 128;
Character c = '嗯';
这种与众不同的实例化方式就是【自动装箱】。简单地说,装箱就是将基本数据类型转换为包装器类型
。128和‘嗯’都是基本数据类型,因为赋值给了包装器类型的变量,就被自动装箱变成了对应的包装器类型的对象。
其实,在编译上述两条代码时,系统实际执行的是:
Integer a = Integer.valueOf(128);
Character c = Character.valueOf('嗯');
valueOf( )方法是每个包装器类型里都有的,用来根据基本数据类型来创建对应的包装类对象。以Integer为例看一下valueOf( )的内部代码:
@Override
public static Integer valueOf(int i) {
return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}
判断完 i 的大小后,会进行这两个操作之一:
1.调用Integer的构造器,返回创建的Integer对象
2.返回SMALL_VALUES的第[i + 128]个元素
先看一下Integer的构造函数:
private final int value;
//初值为int类型的构造器
public Integer(int value) {
this.value = value;
}
// 初值为String类型的构造器
public Integer(String string) throws NumberFormatException {
this(parseInt(string));
}
再看 SMALL_VALUES[i + 128] 是什么:
private static final Integer[] SMALL_VALUES = new Integer[256];
// 为SMALL_VALUES数组赋初值
static {
for (int i = -128; i < 128; i++) {
SMALL_VALUES[i + 128] = new Integer(i);
}
}
可以看到,SMALL_VALUES本身是一个静态的长度为256的Integer数组,那SMALL_VALUES[i + 128]就是该数组的第[i+128]号元素,也是个Integer对象。
为什么valueOf( )要这么写呢,不直接对每个int数值都用构造器返回一个Integer对象?其实是为了优化性能,Integer把 [-128, 127],共256个使用频率较高的小数字缓存了下来,这就出现了这样的现象:
Integer a = 100, b = 100;
System.out.println(a == b); //true
// 这是因为a得到的是SMALL_VALUES[228]的地址,b也是那个地址
// 而==比较的就是栈内存的内容,既然二者都存的是同一个地址,自然就相等了
Integer c = 1000,d = 1000;
System.out.println(c == d); //false
// c是新分配的地址,d也是新分配的地址,二者不一样,所以是false
需要注意的是:
· Integer、Short、Byte、Character、Long这四种包装类的valueOf方法的实现是类似的,都用到缓存。
· Double、Float的valueOf方法的实现是类似的,都只调用构造器。
· Boolea独树一帜,返回TRUE或者FALSE(可不是true和false啊)。
上面说到的TRUE和FALSE是什么呢,看一下定义:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
// TRUE和FALSE都是已经提前创建好的、静态的、不可赋值的Boolen对象
二、自动拆箱
拆箱是什么就好理解多了,拆箱就是将包装器类型转换成基本数据类型
。
下面是几种触发拆箱的情况:
// 1.将包装类赋值给基本数据类型
Integer i = 10; // 装箱
int n = i; // 拆箱
// 2.将包装类和基本数据类型进行==比较
Integer num1 = 400;
int num2 = 400;
System.out.println(num1 == num2); //true
// 3.包装类和基本数据类型进行算术运算
Integer num1 = 100;
int num2 = 100;
Long num3 = 200l;
System.out.println(num1 + num2); //200
System.out.println(num3 == (num1 + num2)); //true
System.out.println(num3.equals(num1 + num2)); //false
倒数第二行为啥是true很好理解,==和算术运算符两边的包装类都会进行拆箱。
最后一行中num3.equals(num1 + num2)为啥是false呢,就需要看一下equal的源码了:
// 不同包装类的equal方法都被重写了,以Long类型为例
@Override
public boolean equals(Object o) {
return (o instanceof Long) && (((Long) o).value == value);
}
可以看到,必须满足两个条件才会返回true值:
1.类型相同
2.值相等
num1 + num2执行时需要拆箱,得到的是值为200的基本数据类型,这就导致和num3值相等但类型不相同,所以返回false。
最后说一下,自动装箱时实际编译了包装类的valueOf方法,那对应的,自动拆箱时编译的是包装类的(以Integer为例)intValue方法:
@Override
public int intValue() {
return value;
}