基于Integer谈一谈java装箱与拆箱
jdk版本问题
在jdk1.5之前即javaSE1.5之前,是不存在自动装箱和自动装箱概念的。
装箱
jdk1.5之前创建Integer对象需要如下代码
Integer i1 = new Integer(1);
在jdk1.5之后,有了自动装箱和自动拆箱的概念后,创建Integer对象只需如下代码
Integer i1 = 1;
这个过程被称为自动装箱。
拆箱
在jdk1.5之前,若想把Integer类型的值赋值给基本数据类型int,需要用到intValue()方法,代码如下:
public class IntegerDemo {
public static void main(String[] args) {
Integer i1 = 1;
int a = i1.intValue();
}
}
在jdk1.5之后,在有了自动拆箱的概念之后,代码便可如下形式
public class IntegerDemo {
public static void main(String[] args) {
Integer i1 = 1;
int a = i1;
}
}
这里将Integer的值直接赋值给基本数据类型i被称为自动拆箱。
如何实现自动装箱和自动拆箱
这里我们可以使用java的反编译指令查看自动装箱和自动拆箱的过程
public class IntegerDemo {
public static void main(String[] args) {
Integer i1 = 1;
int i = i1;
}
}
反编译结果
我们给Integer对象直接赋值的时候,JVM虚拟机会帮我们自动调用Integer.valueOf方法,Integer.valueOf方法返回值为Integer类型,这就是所谓的自动装箱的原理。
我们将Integer对象i1的值直接赋值给基本数据类型i的时候,JVM虚拟机会帮我们调用Integer.intValue方法,Integer.intValue方法的返回值类型为int,这就是自动拆箱的过程。
其他的包装类,例如Double、Long等等都存在自动装箱和自动拆箱,原理与Integer相同!
面试中关于装箱和拆箱的问题
public class IntegerDemo {
public static void main(String[] args) {
Integer i1 = 50;
Integer i2 = 50;
Integer i3 = 200;
Integer i4 = 200;
Integer i5 = new Integer(50);
Integer i6 = new Integer(50);
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
System.out.println(i3.equals(i4)); // true
System.out.println(i5 == i6); // false
System.out.println(i1 == i5); // false
System.out.println(i1.equals(i5)); // true
}
}
解析
这里为什么i1==i2得到true而i3==i4却得到false
这里涉及到了Integer源码中的IntegerCache内部类,下面解析下IntegerCache类
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
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);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
从代码不难看出,IntegerCache类中存在一个Integer cache[],这是因为jdk开发人员考虑到integer的经常使用,把-128~127共256个数值存放于256个integer对象中,然后把这些integer对象存放在了Integer cache[]数组中,在编写代码时,若直接给integer对象直接赋值,会先去Integer cache[]数组中查询,若该值在Integer cache[]数组中,直接使用Integer cache[]数组中的integer对象,不需再新new出integer对象。
所以也不难得出为何i1==i2为true而i3==i4为false。
而i5和i6都是new出来的integer对象,所以i5==i6肯定为false。
而integer类中重写了equals方法,所以在执行equals方法时,比较的是数值大小,所以i3.equals(i4)和i1.equals(i5)结果都为true。
另外一种情况
public class IntegerDemo {
public static void main(String[] args) {
Integer i1 = 1;
Integer i2 = 2;
Long l1 = 1L;
Long l2 = 2L;
Long l3 = 3L;
System.out.println(l3 == (i1 + i2)); // true
System.out.println(l3.equals(i1 + i2)); // false
}
}
这里需要解释一下包装类对象在进行==运算时需要注意的地方
如果==号左右两边均为包装类对象的引用,则进行比较的是两边包装类对象的引用是否指向的是同一个对象,即它们指向的“地址”是否相同(这里说明一下,java中没有地址的概念,这里只是便于理解)。
如果等号两边出现了算数表达式(即出现了算数运算符),则两边都会触发自动拆箱,比较的不再是是否指向的是不是同一个对象,而是比较的数值大小。
那么上述代码中第一行得出true也不难理解。
包装类中的equals方法这里以Integer和Long为例
Integer的equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
Long的equals方法
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
这里不难看出,当包装类对象使用equals方法时,equals不会进行类型转换,从而得出上述代码第二行应输出false。
浮点数的包装类
浮点数包装类中不存在cache数组,因为浮点数在任意大小区间都有无数多个数,下面代码可以证明。
public class IntegerDemo {
public static void main(String[] args) {
Double d1 = 1.0;
Double d2 = 1.0;
System.out.println(d1 == d2); //false
}
}
这里输出false可以证明在浮点数(Double和Float)中不存在cache数组概念。
Boolean包装类
直接看代码
public class IntegerDemo {
public static void main(String[] args) {
Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;
System.out.println(b1 == b2); // true
System.out.println(b3 == b4); // true
System.out.println(b1 == b3); // false
}
}
这里需要说明一下
在Boolean类的源码中存在两个final定义的常量
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
我们再来看一下Boolean的valueOf方法
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
我们可以理解为Boolean包装类中也存在有cache数组,只不过数组中只有两个对象,TRUE和FALSE,每当Boolean进行自动装箱时,总是从这两个对象中取,所以也不难理解上述代码中得到的结果。