1.什么是装箱与拆箱、自动装箱与自动拆箱?
Java为每种基本数据类型都提供了对应的包装器类型,int对应Integer、boolean对应Boolean。。。
所以,装箱就是将基本数据类型转换为包装器类型;拆箱就是将包装器类型转换为基本数据类型。那自动装箱与自动拆箱也很好理解了,就是我们不用在代码里面显式的写装箱与拆箱的代码,由编译器帮我们完成这一操作。
在Java SE5之前
Integer i = new Integer(1); // 装箱
int j = i.intValue(); // 拆箱
之后
Integer i = 1; // 装箱
int j = i; // 拆箱
2.自动装箱和拆箱多了什么步骤?
public class Test {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer i = 1;
int j = i;
}
}
我们先编译这段代码获得.class文件,然后使用javap -c反编码文件获得字节码指令。
invokestatic的意思是调用了静态方法, 后面的注释也告诉了我们调用了什么方法。 invokevirtual是调用实例方法。
得出的结论是自动装箱会调用valueOf方法,自动拆箱则会调用xxxValue方法(xxx是基本类型)。
3.常见的问题
你们知道下面这段代码的结果吗?
public class Test {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer a1 = 1;
Integer a2 = 2;
Integer a3 = 126;
Integer a4 = 126;
Integer a5 = 127;
Integer a6 = 128;
Integer a7 = 128;
Integer b1 = 1024;
Integer b2 = 1024;
Long c1 = 3L;
Long c2 = 0L;
Double d1 = 1D;
Double d2 = 1D;
System.out.println(a1 + a3 == a5);
System.out.println(a2 + a3 == a6);
System.out.println(a3 == a4);
System.out.println(a6 == a7);
System.out.println(a6 - a5 == a1);
System.out.println(a6.equals(a1 + a5));
System.out.println(a5.equals(a1 + a3));
System.out.println(b1 == b2);
System.out.println(b1 - a1 == b2 - a1);
System.out.println(c1 == a1 + a2);
System.out.println(c1.equals(a1 + a2));
System.out.println(c1.equals(a1 + a2 + c2));
System.out.println(d1 == d2);
}
}
还是让我们先看看valueOf、xxxValue的源码吧。已Integer为例。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这是Integer类valuOf的一段源码,当装箱时,如果值在[IntegerCache.low, IntegerCache.high]范围之间是不会生成新对象的,对象会从缓存里取出。那这个范围是多少呢?
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
查看IntegerCache的源码可知,low的值为-128,而high的值可以指定,默认是127。也就是说在正常情况下[-128, 127]内的int在装箱的时候不会产生新对象。
那Long、Double呢?
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
public static Double valueOf(double d) {
return new Double(d);
}
我们发现Long也是有缓存的,缓存是-128~127,无值指定最高范围。Double是没有缓存的,就说明只要装箱就会生成一个新对象。
最后我们看一下Integer的equals,结论就是传入的对象必须是同类型的才会进行数值比较,否则直接false。
eq: Integer 1 不等于 Long 1
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
现在就可以解决上面的问题了
public class Test {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Integer a1 = 1;
Integer a2 = 2;
Integer a3 = 126;
Integer a4 = 126;
Integer a5 = 127;
Integer a6 = 128;
Integer a7 = 128;
Integer b1 = 1024;
Integer b2 = 1024;
Long c1 = 3L;
Long c2 = 0L;
Double d1 = 1D;
Double d2 = 1D;
// 因为对象无法进行加法运行, 会自动拆箱, 所以这里进行的是数值比较
System.out.println(a1 + a3 == a5); // true
System.out.println(a2 + a3 == a6); // true
// 在缓存范围内, 是同一个对象
System.out.println(a3 == a4); // true
// 不在缓存范围内, 不是同一个对象
System.out.println(a6 == a7); // false
System.out.println(a6 - a5 == a1); // true
// 这里进行了3步
// 1. 对象拆箱进行加法运行
// 2. 对结果进行装箱
// 3. 对参数进行拆箱进行数值比较
System.out.println(a6.equals(a1 + a5)); // true
System.out.println(a5.equals(a1 + a3)); // true
System.out.println(b1 == b2); // false
System.out.println(b1 - a1 == b2 - a1); // true
System.out.println(c1 == a1 + a2); // true
// 因为a1 + a2的结果是int, 自动装箱为Integer, 无法转化为Long, 直接返回false
System.out.println(c1.equals(a1 + a2)); // false
// a1 + a2 + c2结果为long,可以进行数值比较
System.out.println(c1.equals(a1 + a2 + c2)); // true
// Double没有缓存
System.out.println(d1 == d2); // false
}
}
4.性能问题
频繁的装箱性能是非常的低下, 因为要频繁的创建、回收对象。特别是Double,Double没有缓存,每装箱一次就会产生一个新的对象。
public class Test {
public static void main(String[] args) {
// TODO 自动生成的方法存根
int i = 0;
Integer j = 0;
long startTime, endTime;
startTime = System.currentTimeMillis(); //获取开始时间
while (i < 999999999) {
++i;
}
endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("基本数据类型:" + (endTime - startTime) + "ms"); //输出程序运行时间
startTime = System.currentTimeMillis(); //获取开始时间
while (j < 999999999) {
++j;
}
endTime = System.currentTimeMillis(); //获取结束时间
System.out.println("包装类型:" + (endTime - startTime) + "ms"); //输出程序运行时间
}
}