前言
之前虽然略有听闻自动装箱、自动拆箱,却一直不清楚装箱、拆箱到底是什么,直到今天看到一段有趣的小程序,决定研究一番装箱与拆箱。
- 基本概念
- 自动装箱:八种基本数据类型在某些条件下使用时候,会自动变为对应的包装类型。(上面的代码,就是自动装箱的一种)
- 自动拆箱:八种包装类型在某些条件下使用时候,会自动变成对应的基本数据类型。
- 简单来说,代码表示是这样的
- 自动装箱:Integer i = 10;(int -> Integer)
- 自动拆箱:int n = i;(Integer -> int)
附:八种基本数据类型
基本类型 | 占用空间(Byte) | 表示范围 | 包装类型 |
---|---|---|---|
boolean | 1/8 | true/false | Boolean |
char | 2 | -128~127 | Character |
byte | 1 | -128~127 | Byte |
short | 2 | -2^15 ~ 2^15-1 | Short |
int | 4 | -2^31 ~ 2^31-1 | Integer |
long | 8 | -2^63 ~ 2^63-1 | Long |
float | 4 | -3.403E38 ~ 3.403E38 | Float |
double | 8 | -1.798E308 ~ 1.798E308 | Double |
认识自动装箱
回看看开头的代码,第一次输出true,很合乎情理,而第二次输出却是false,这就很让人疑惑了。
解析:
当包装器类进行“==”比较时,内部会调用 Integer.valueOf方法进行自动装箱(int -> Integer)
从源码中可见,Integer对象内部有IntegerCache类,可缓存(-128~127范围的数值),如果超过了,则会返回一个新的Integer类。由于“==”比较的是内存地址,因此,在“-128~127”数值范围内,比较的是同一个对象,得到true,而超过了该范围,则是返回自动装箱后的新对象,因此得到false。
总结:
- Integer、Short、Byte、Character、Long这几个包装类的valueOf方法的实现是类似的
- Double、Float的valueOf方法的实现是类似的
- Boolean的valueOf方法的实现是个三目运算,形如
return (b ? TRUE : FALSE);
认识自动拆箱
再看一段代码演示自动拆箱:
解析:
当与基本类型进行“==”比较时,包装器类会调用intValue方法进行自动拆箱(Integer -> int)
从源码中可见,直接返回真实值,没有范围限制,因此两次输出均为true
装箱与拆箱如何实现?
看到这里,可能会有人疑问:“从哪里可用看出自动装箱调用了valueOf方法而自动拆箱则调用了intValue方法呢?”答案便是 - 通过反编译class文件,下面作简单演示:
编译后,在控制台中使用 javap -c StudyJava 命令可得到:
从反编译得到的字节码中不难看出,装箱(int -> Integer)时候的确调用了Integer.valueOf方法;拆箱时则调用Integer.intValue方法
需要注意的地方
Double、Float的valueOf方法的实现与Integer、Short、Byte、Character、Long这几个类的实现方法有区别。
原因:在一定范围内,整数数值是有限的,而对于浮点数,则不是。1234567891011121314151617/*** Returns a {@code Double} instance representing the specified* {@code double} value.* If a new {@code Double} instance is not required, this method* should generally be used in preference to the constructor* {@link #Double(double)}, as this method is likely to yield* significantly better space and time performance by caching* frequently requested values.** @param d a double value.* @return a {@code Double} instance representing {@code d}.* @since 1.5*/public static Double valueOf(double d) {//并没有缓存,直接返回一个新的实例化对象return new Double(d);}明白源码中的实现后,相信大家对下面代码输出结果也都理解了吧~
12345678910111213public class StudyJava{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//fasle接下来再提一下Boolean类,装箱valueOf实现代码如下
1234567891011121314151617181920/*** Returns a {@code Boolean} instance representing the specified* {@code boolean} value. If the specified {@code boolean} value* is {@code true}, this method returns {@code Boolean.TRUE};* if it is {@code false}, this method returns {@code Boolean.FALSE}.* If a new {@code Boolean} instance is not required, this method* should generally be used in preference to the constructor* {@link #Boolean(boolean)}, as this method is likely to yield* significantly better space and time performance.** @param b a boolean value.* @return a {@code Boolean} instance representing {@code b}.* @since 1.4*/public static Boolean valueOf(boolean b) {//TRUE、FALSE为两个内部定义的静态成员,这里直接返回两者其一// public static final Boolean TRUE = new Boolean(true);// public static final Boolean FALSE = new Boolean(false);return (b ? TRUE : FALSE);}既然返回的是内部定义静态成员,只要值相同,那么就是同一个对象,因此下面代码就很好理解啦
12345678910111213public class StudyJava{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稍微复杂一点呢?
123456789101112131415161718192021222324252627282930public class StudyJava{public static void main(String[] args){Integer i1 = 1;Integer i2 = 2;Integer i3 = 200;int i4 = i1 + 1;int i5 = 200;Integer i6 = 200;Long l1 = 3L;Long l2 = 2L;System.out.println(i4 == i2); - trueSystem.out.println(i2.equals(i4)); - trueSystem.out.println(i3 == i5); - trueSystem.out.println(i3.equals(i5)); - trueSystem.out.println(i3 == i6); - falseSystem.out.println(i3.equals(i6)); - true// System.out.println(l2==i2);//该行为错误代码,无法通过编译,类型不同System.out.println(l1 == (i1 + i2)); - true//可见其中进行了一些操作,使得两者可以比较System.out.println(l2.equals(i1 + i2)); - false}}//输出结果://true//true//true//true//false//true//true//false
其中值得注意的,是最后两行代码:
“l1 == (i1 + i2)”其中包含了算术运算,会触发自动拆箱(算术运算需要自动拆箱,各自调用intValue方法得到基本类型),再进行自动装箱,最终他们进行了数值比较,因此可以正常编译;
而“l2.equals(i1 + i2)”则是,先触发“i1 + i2”的自动拆箱(算术运算需要自动拆箱,各自调用intValue方法得到基本类型),算术运算得到数值后,再进行数值对应类型的自动装箱(valueOf),得到Integer实例对象,最后再进行equals比较。
结语
弄懂原理以及自动装、拆箱的时机,外加一点心细,就能较好地掌握本知识点啦,建议大家多动手实验,亲自验证一番,肯定有更多的收获的。