Java 1.5后引入了自动拆箱(Unboxing)和自动装箱机制(Autoboxing),用于实现基础数据类型和包装器间的隐式转换。基础数据类型和包装器类型的对应如下:
自动拆装箱的实现
代码
public static void main(String[] args) {
Integer i = 10;
int n = i;
}
反编译后
public class Demo {
public static void main(String[] args) {
Integer i = Integer.valueOf((int)10);
int n = i.intValue();
}
}
通过代码和反编译可知,自动装箱通过包装器类型的valueOf()方法实现,而自动拆箱是通过调用包装器类型xxxValue()方法实现(xxx为基础数据类型)。
三元运算符中的拆装箱
public static void main(String[] args) {
Integer i = 1;
System.out.println(i == 1 ? true: false);
}
反编译后
public static void main(String[] args) {
Integer i = Integer.valueOf((int)1); //自动装箱
System.out.println((boolean)(i.intValue() == 1)); //自动拆箱
}
为了节省内存、提高性能,部分包装器类型缓存了值在[-128,127]区间内的对象。这些包装器类型包括Character、Byte、Short、Integer、Long。以下以Integer类型为例:
Integer源码(Java8)的valueOf()函数实现:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
默认情况下,缓存类IntegerCache的low和high的值分别为-128和127。可以看出,当整数处于[-128,127]之间时,会从缓存中获取对象,否则创建新对象。
其中,IntegerCache为静态内部类,在该类被加载时会执行静态代码块,读取缓存区间的右边界,并在对象数组cache中创建区间内的所有对象,因此在IntegerCache获取的值相同的对象是同一对象。
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 =
sun.misc.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() {}
}
这五种基础类型的valueOf()函数大致相同,但double和float的valueOf()函数却不一样:
public static Double valueOf(double d) {
return new Double(d);
}
public static Float valueOf(float f) {
return new Float(f);
}
通过源码可知,它总会直接创建一个新对象,之所以这样是因为整型区间的对象个数是有限的,而浮点类型该区间段的对象个数不是有限的。
注意:缓存策略只针对自动装箱的情形有效,而对于直接创建的包装器类型无效,即不会被缓存。
Boolean源码(Java8)的valueOf()函数实现:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE); //TRUE 和 FALSE作为两个对象常量存在于cache中
}
综合应用
示例1
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
Double d1 = 100.0;
Double d2 = 100.0;
Double d3 = 200.0;
Double d4 = 200.0;
System.out.println(d1 == d2); // false
System.out.println(d3 == d4); // false
Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;
System.out.println(b1 == b2); // true
System.out.println(b3 == b4); // true
通过自动装箱的源码,再判断值是否再区间内,就可以轻易知道是否是同一个对象。
示例2
public class Test {
public static Integer modify(Integer i) {
i++;
return i;
}
public static void main(String[] args) {
Integer i = 200;
System.out.println(i == modify(i)); // false
}
}
在执行modify()函数时,变量i进行了拆箱和装箱两步,拆箱做运算,装箱后将新的对象赋给了原变量。因此输出结果为false。在这可能不太直观,那让我们来编译一下:
public static Integer modify(Integer i) {
Integer n = i;
Integer n2 = i = Integer.valueOf((int)(i.intValue() + 1));
return i;
}
public static void main(String[] args) {
Integer i = Integer.valueOf((int)200);
System.out.println((boolean)(i == Demo.modify((Integer)i)));
}
通过反编译,具体是怎么运行的,一清二楚了。
示例3
public static void main(String[] args) {
Integer i1 = 2;
Integer i2 = 2;
Integer i3 = i1 + i2;
int i4 = i1 + i2;
Long l1 = 4L;
Long l2 = 0L;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // true
System.out.println(i3 == (i1 + i2)); // true
System.out.println(l1.equals(i1 + i2)); // false
System.out.println(l1.equals(i1 + i2 + l2)); // true
}
反编译
public static void main(String[] args) {
Integer i1 = Integer.valueOf((int)2);
Integer i2 = Integer.valueOf((int)2);
Integer i3 = Integer.valueOf((int)(i1.intValue() + i2.intValue()));
int i4 = i1.intValue() + i2.intValue();
Long l1 = Long.valueOf((long)4L);
Long l2 = Long.valueOf((long)0L);
System.out.println((boolean)(i1 == i2));
System.out.println((boolean)(i3.intValue() == i4));
System.out.println((boolean)(i3.intValue() == i1.intValue() + i2.intValue()));
System.out.println((boolean)l1.equals((Object)Integer.valueOf((int)(i1.intValue() + i2.intValue()))));
System.out.println((boolean)l1.equals((Object)Long.valueOf((long)((long)(i1.intValue() + i2.intValue()) + l2.longValue()))));
}
综上所述:
● 触发自动装箱的场景:将基本数据类型值赋予包装类型变量时,会自动装箱。
● 触发自动拆箱的场景:Integer做”>”、”>=”、”<”、”<=”操作时;Integer与int比较时;当有运算符时,如加、减、乘、除时……会比较数值大小,则自动拆箱。
● Boolean的两个值true和false都是在内存中,与new出的不是一块空间。
● Byte的256个值都在内存中,与new出的对象不是一块空间。
● 在反射当中,对于Integer属性不能使用field.setInt()和field.getInt()操作。
● 包装类型”==”时,判断是否指向同一个对象。