Java基础(十四)--装箱、拆箱详解
Java中基本数据类型都有相对应的包装类
什么是装箱?什么是拆箱?
在Java SE5之前,Integer是这样初始化的
Integer i = new Integer(10);
而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:
Integer i = 10;
这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。
关于boolean占用几个字节,请参考:https://www.jianshu.com/p/2f663dc820d0
public static void main(String[] args) { Integer i = 10; //装箱 int i1 = i; //拆箱 }
装箱:自动将基本数据类型转换为包装器类型
拆箱:自动将包装器类型转换为基本数据类型
有了基本数据类型,为什么还需要包装类?
Java是面向对象的,而基本数据类型和对象完全不搭边。只能单纯使用数值时节省内存空间但是无法进行相关函数操作
包装类型为其添加了属性和方法,丰富了基本类型的操作,但有占用更多的内存空间
为了相互转换比较方便,所以通过包装基本数据类型就可以变成一个对象
PS:
往集合里保存数据的时候,假如add一个int类型,都是自动装箱成Integer的,因为ArrayList、HashMap等,保存的都是Object
public static void main(String[] args) { int i = 1; List list = new ArrayList(); list.add(i); }
我们通过javap反编译查看:
public static void main(java.lang.String[]); Code: 0: iconst_1 1: istore_1 2: new #2 // class java/util/ArrayList 5: dup 6: invokespecial #3 // Method java/util/ArrayList."<init>":()V 9: astore_2 10: aload_2 11: iload_1 12: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 15: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 20: pop 21: return }
第12行,通过调用Integer.valueOf(),实现把int自动装箱转换为Integer,证实了上面的说话
自动装箱、拆箱如何实现:
public static void main(String[] args) { Integer i = 10; int i1 = i; }
public class com.it.Test { public com.it.Test(); 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 10 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.valueOf(int)实现自动装箱,通过Integer.intValue()实现自动拆箱
其他基本数据类型,也都是相似的过程,例如char
public static void main(String[] args) { Character character = 'a'; char c = character; }
public static void main(java.lang.String[]); Code: 0: bipush 97 2: invokestatic #2 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character; 5: astore_1 6: aload_1 7: invokevirtual #3 // Method java/lang/Character.charValue:()C 10: istore_2 11: return
概括自动装箱、拆箱实现:
装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的(xxx代表对应的基本数据类型)
相关面试题:
1、Integer相关代码的输出结果是什么?
public static void main(String[] args) { Integer i1 = 100; Integer i2 = 100; Integer i3 = 200; Integer i4 = 200; System.out.println(i1==i2); System.out.println(i3==i4); }
输出结果:
true false
结果可能跟你想象的不同,i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是
Integer的valueOf方法的具体实现:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则
创建一个新的Integer对象。
上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同
的对象。
2.Double相关代码的输出结果是什么?
public static void main(String[] args) { Double i1 = 100.0; Double i2 = 100.0; Double i3 = 200.0; Double i4 = 200.0; System.out.println(i1==i2); System.out.println(i3==i4); }
false false
也许有人会认为跟上面一道题目的输出结果相同,但是事实上却不是。
Double.valueOf()源码如下:
public static Double valueOf(double d) { return new Double(d); }
Double.valueOf()与Integer.valueOf()为什么实现不同?
原因是在某个范围内的整型数值的个数是有限的,而浮点数却不是。
PS:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。
Double、Float的valueOf方法的实现是类似的。
3.Boolean相关代码输出结果是什么:
public static void main(String[] args) { Boolean i1 = false; Boolean i2 = false; Boolean i3 = true; Boolean i4 = true; System.out.println(i1==i2); System.out.println(i3==i4); }
输出结果:
true true
Boolean.valueOf()源码如下:
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); }
4.谈谈Integer i = new Integer(xxx)和Integer i =xxx;的区别
当然,这个题目属于比较宽泛类型的。但是要点一定要答上,我总结一下主要有以下这两点区别:
1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;
2)在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)。
5.下面程序的输出结果是什么?
public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; Long h = 2L; System.out.println(c==d); System.out.println(e==f); System.out.println(c==(a+b)); System.out.println(c.equals(a+b)); System.out.println(g==(a+b)); System.out.println(g.equals(a+b)); System.out.println(g.equals(a+h)); }
先别看输出结果,自己想一下这段代码的输出结果是什么。
这里面需要注意的是:当 "=="运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数
是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
另外,对于包装器类型,equals方法并不会进行类型转换。明白了这2点之后,上面的输出结果便一目了然:
输出结果:
true false true true true false true
1和2:结果应该没疑问
3:由于a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。
4:而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的
数值之后,便调用Integer.valueOf方法,再进行equals比较。
5:g==(a+b),a+b会先触发自动拆箱过程,比较的是数值是否相等,Integer会自动转为Long,所以结果是true
6:g.equals(a+b),我们可以反编译查看:
public static void main(java.lang.String[]); Code: 0: iconst_1 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: iconst_2 6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 9: astore_2 10: ldc2_w #3 // long 3l 13: invokestatic #5 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 16: astore_3 17: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 20: aload_3 21: aload_1 22: invokevirtual #7 // Method java/lang/Integer.intValue:()I 25: aload_2 26: invokevirtual #7 // Method java/lang/Integer.intValue:()I 29: iadd 30: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 33: invokevirtual #8 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z 36: invokevirtual #9 // Method java/io/PrintStream.println:(Z)V 39: return
过程:
1、Integer a = 1; //通过Integer.valueOf(int)进行自动装箱 2、Integer b = 2; //通过Integer.valueOf(int)进行自动装箱 3、Long g = 3L; //通过Long.valueOf(long)进行自动装箱 4、System.out.println(g.equals(a+b));
//a+b,a和b首先通过Integer.intValue()转换为int,然后执行+,也就是iadd指令
//然后遇到equals(),通过Integer.valueOf(int)将结果转换为Integer
//最后Long.equals(Integer),两种类型不同,得到false
//返回输出
7:我们已经能够想象g.equals(a+h)的字节码了,最后是Long和Long通过equals比较,得到true
内容转载自:深入剖析Java中的装箱和拆箱,有修改,添加内容