Java中的装箱和拆箱
1. 什么是装箱,拆箱?
装箱:自动将基本数据类型转换为包装器类型(引用类型)。
拆箱:自动将包装器类型(引用类型)转换为基本数据类型。
举个栗子:
Integer in = 111; //装箱的时候自动调用的是Integer的valueOf(int)方法。
int i = in; //拆箱的时候自动调用的是Integer的intValue方法。
进行反编译:javap -c .\Main.class
(这里使用idea自带的反编译插件)
PS D:\work-idea\java\target\classes\com\ea\day03> javap -c .\Main.class
Compiled from "Main.java"
public class com.ea.day03.Main {
public com.ea.day03.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 111
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: aload_1
7: invokevirtual #3 // Method java/lang/Integer.intValue:()I
10: istore_2
11: return
}
从反编译得到的字节码可以看出:
当执行装箱(Integer in = 111;)时,Java会执行Integer.valueOf函数;
拆箱(int i = in;)时,执行Integer.intValue函数。
所以,
装箱的过程:装箱过程是通过包装器的 valueOf 方法实现的;
拆箱的过程:拆箱过程是通过包装器的 xxxValue 方法实现的。(xxx代表对应的基本数据类型:intValue等)
2. 基本数据类型对应的包装器类型(引用类型)
基本数据类型(字节数) | 包装器类型(引用类型) |
---|---|
byte(1) | Byte |
short(2) | Short |
int(4) | Integer |
long(8) | Long |
float(4) | Float |
double(8) | Double |
char(2) | Character |
boolean | Boolean |
3. 常考面试题
3.1 Integer 的 “==” 与 “equals”
判断下面的输出结果(可以自己试一下,答案在此节最后):
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
System.out.println(i1.equals(i2));
System.out.println(i3.equals(i4));
-
对于“==”,结果是不一样的,分析 Integer的valueOf(int) 源码:
public final class Integer extends Number implements Comparable<Integer> { public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } //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函数调用的IntegerCache.cache[i+128]可以看出:valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
所以,i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象,所以 i1=i2,i3≠i4。 -
对于equals:会进行intValue()转换拆箱再判断数值是否相等。
//Integer 源码中的equals函数 private final int value; public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
上面程序的输出结果为:
true false true true
3.2 Double 的 “==” 与 “equals”
判断下面的输出结果(可以自己试一下,答案在此节最后):
Double d1 = 100.0;
Double d2 = 100.0;
Double d3 = 200.0;
Double d4 = 200.0;
System.out.println(d1==d2);
System.out.println(d3==d4);
System.out.println(d1.equals(d2));
System.out.println(d3.equals(d4));
Double也是应用到valueOf函数,但是与Integer的有所不同:
public static Double valueOf(double d) {return new Double(d);}
这是为什么呢?因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。
所以,Double引用类型的“==”的结果为false;equals会进行doubleToLongBits()转换再进行数值判断。
Integer、Short、Byte、Character、Long 这几个类的valueOf方法的实现是类似的。
Double、Float 的valueOf方法的实现是类似的。
上面程序的输出结果为:
false false true true
3.3 Boolean 的 “==” 与 “equals”
判断下面的输出结果(可以自己试一下,答案在此节最后):
Boolean b1 = true;
Boolean b2 = true;
Boolean b3 = false;
Boolean b4 = false;
System.out.println(b1==b2);
System.out.println(b3==b4);
System.out.println(b1.equals(b2));
System.out.println(b3.equals(b4));
查看Boolean的valueOf的源码:
//Boolean的部分源码
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
可以看出,在装箱时,已经转换为一个静态的值:new Boolean(true/false);
。
所以结果都是true。
3.4 谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
Integer i = new Integer(xxx);
不会触发装箱的动作,Integer i =xxx;
会触发;- 一般情况下,
Integer i =xxx;
的性能会比Integer i = new Integer(xxx);
的性能高(但也不是绝对的)。
3.5 算数运算中 的 “==” 与 “equals”
见题:
Integer a = 1;
Integer b = 2;
Integer c = 3;
Long m = 3L;
Long n = 2L;
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(m==(a+b));
System.out.println(m.equals(a+b));
System.out.println(m.equals(a+n));
铭记两点:
- 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象;
而如果其中有一个操作数是表达式(即包含算术运算),则比较的是数值(即会触发自动拆箱的过程)。 - 对于equals():先触发自动拆箱过程,再触发自动装箱过程。而且包装器类型的equals方法 不会进行类型转换。
所以:
c==(a+b) | 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。 |
c.equals(a+b) | 先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较(先拆箱为int+int,然后装箱为Integer,然后再equals比较)。 |
m==(a+b) | a+b返回的是int类型,再与m的值进行比较。 |
m.equals(a+b) | 先拆箱进行运算,变为int+int,计算出的是int类型,装箱时调用的是Integer.valueOf,m为Long,所以返回false。 |
m.equals(a+n) | 先拆箱,变为int+long,计算出的结果为long类型,装箱时调用的是Long.valueOf,m为Long,所以返回true。 |
结果为:
true true true false true
4. 坑
Integer in = null;
int i = in;
上面两个单独没有错,但放在一起会报空指针异常。在执行 intValue 时,参数不可为null,所以,有拆箱操作时一定要特别注意封装类对象是否为null。
5. 总结
- 装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。
- equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱。
- 当两种不同类型用
==
比较时,包装器类的需要拆箱, 当同种类型用==
比较时,会自动拆箱或者装箱。 - 必须满足两个条件才为true: 1、类型相同;2、内容相同 。
- 当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象;而如果其中有一个操作数是表达式(即包含算术运算),则比较的是数值(即会触发自动拆箱的过程)。
- 对于equals():先触发自动拆箱过程,再触发自动装箱过程。而且包装器类型的equals方法 不会进行类型转换。
- 建议慎用包装类,避免隐式转换,合理使用。注意空指针问题。
若有不正之处,请谅解和批评指正,谢谢~
转载请标明:
https://blog.youkuaiyun.com/vihem/article/details/120746761