装箱与拆箱
装箱与拆箱
什么是装箱与拆箱
描述
语言描述,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
代码描述就是:
Integer integer = 100; //自动装箱
int i = integer; //自动拆箱
基本技术类型对应的包装器类型表:
数据类型 | 包装器类型 |
---|---|
int(4字节) | Integer |
byte(1字节) | Byte |
short(2字节) | Short |
long(8字节) | Long |
float(4字节) | Float |
double(8字节) | Double |
char(2字节) | Character |
boolean(未定) | Boolean |
如何实现装箱与拆箱
装箱与拆箱的代码
public class IntegerAndInt {
public static void main(String[] args) {
// TODO Auto-generated method stub
Integer integer = 100; //自动装箱
int i = integer; //自动拆箱
}
}
反编译class文件
从反编译得到的字节码内容可以看出,在装箱的时候自动调用了Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。
因此,装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的xxxValue方法实现的。
valueOf方法
public static Integer valueOf(int i) {
return i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];
}
首先判断数值大小,如果数值大于等于128或者小于-128,则创建一个Integer对象返回,否则,则返回SMALL_VALUES的i+128的数值。
接着,查看以下Integer的构造函数:
private final int value;
public Integer(int value) {
this.value = value;
}
public Integer(String string) throws NumberFormatException {
this(parseInt(string));
}
Integer定义了一个value变量,创建一个Integer对象时,就会给这个变量初始化。第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。
再看一下SMALL_VALUES[i+128]是什么:
private static final Integer[] SMALL_VALUES = new Integer[256];
SMALL_VALUES是一个静态的Integer数组对象,也就是说valueOf返回的都是一个Integer对象。
通过分析可以看到装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能。
initValue方法
@Override
public int intValue() {
return value;
}
intValue方法直接返回了value值。
装箱与拆箱需要注意的一些问题
例子一
public class Main{
public static void main(String[] args) {
Integer i1 = 66, i2 = 66, i3 = 166, i4 = 166;
System.out.println(i1 == i2);//true
System.out.println(i3 == i4);//false
}
}
看上面的代码可以发现,两个比较的结果不相同,再结合上面的装箱原理,128~-127的装箱是直接返回的SMALL_VALUES数组中存储的值,所以i1和i2的装箱结果返回的是同一个变量,所以是相等的,而i3和i4是返回的新创建的变量,两个变量是不同的,所以不相等。
例子二
Integer a = new Integer(6);
Integer b = 6; // 将6自动装箱成Integer类型
int c = 6;
System.out.println(a == b); // false 两个引用没有引用同一对象
System.out.println(a == c); // true a自动拆箱成int类型再和c比较
a是一个创建的Integer对象,而b是自动装箱,是SMALL_VALUES数组中存储的值,所以a和b是不同的对象,不相等,而c是int类型的值,a与c相比是会先拆箱为int类型的6,两个6数值相比,故相等。
例子三
Integer i1 = new Integer(6);
Integer i2 = 6;
System.out.println("i1.equals(i2):"+(i1.equals(i2))); //true
会发现同样的对象,==与equals的结果是不同,先看一下equals源码:
@Override
public boolean equals(Object o) {
return (o instanceof Integer) && (((Integer) o).value == value);
}
发现equals方法是比较value值相同的,比较的内容的本身。
例子四
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
System.out.println(num3 == (num1+num2)); //true
当一个基本类型数据域封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。
而num3.equals(num1+num2)为false的原因是,num1+num2的类型不是Long,所以为false。
Long的equals方法:
@Override
public boolean equals(Object o) {
return (o instanceof Long) && (((Long) o).value == value);
}
而(num3 (num1+num2))为true,当运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
例子五
Integer integer=null;
int i=integer;
上面的代码可以通过编译,但是在运行时,就会抛出空指针异常。integer是Integer类型的对爱选哪个,可以指向null,但是对integer进行拆箱的时候,就是对一个null对象调用valueOf方发,所以会抛出空指针异常。
注意
Integer、Short、Byte、Charater、Long这几个类的valueOf方法的实现是类似的,Double和Float的valueOf方法的实现是类似的,是没有想Integer一样的SMALL_VALUE数组的。
Double的valueOf方法:
public static Double valueOf(double d) {
return new Double(d);
}
Float的valueOf方法:
public static Float valueOf(String s) throws NumberFormatException {
return new Float(FloatingDecimal.getThreadLocalInstance().readJavaFormatString(s).floatValue());
}
还有,Boolean的valueOf方法:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
所以不管对象是不是同一个,只要对象的值时相同的,就是相等的。
5.总结
Java通过自动装箱和拆箱的机制,节省了部分内存开销和创建对象的开销(Integer的128~-127常用值节省开支),提高了效率同时简化了代码,不用每次需要程序员转换类型。在比较数值相等时,尽量使用equals()方法。
参考文章
https://www.cnblogs.com/wang-yaz/p/8516151.html 详解Java的自动装箱与拆箱(Autoboxing and unboxing)
https://www.cnblogs.com/dolphin0520/p/3780005.html 深入剖析Java中的装箱和拆箱