本篇将探讨自动装箱(Autoboxing)和拆箱(Unboxing)的相关概念、常见场景、可能涉及的问题及陷阱。目录结构如下:
1、自动装箱(Autoboxing)和拆箱(Unboxing)
自动装箱和拆箱的概念如下:
- 自动装箱:直接将一个原始数据类型传给其相应的包装器类型(wrapper class),编译器会自动转换成对应的包装器类型,这就是自动装箱(Autoboxing)。
- 拆箱:将一个包装器类型的对象赋值给其相应的原始数据类型变量,编译器会自动转换成对应的原始数据类型,则称为拆箱(Unboxing)。
- 由此可见,拆箱与装箱是相反的操作,而且都是编译器添加的自动处理。
示例如下:
// 自动装箱 Autoboxing // Xxx.valueOf(xxx i) // 编译器会转成 Integer.valueOf(int i) 进行自动装箱 Integer i = 100; Integer j = Integer.valueOf(100); System.out.println("i == j --> " + (i == j)); j = Integer.valueOf(i); System.out.println("i == j --> " + (i == j)); // 直接创建包装器类型的对象 Integer k = new Integer(100); System.out.println("i == k --> " + (i == k)); // 拆箱 Unboxing // Xxx..xxxValue() // 编译器会转成 Integer.intValue() 进行拆箱 int m = i; System.out.println("i == m --> " + (i == m));
2、自动拆箱和装箱的常见场景
这里主要涉及到到几类常见场景:运算符操作、方法参数和方法返回值。这些都是比较常见的存在自动拆箱和装箱过程的场景。详细说明如下:
- 运算符操作
- 赋值操作
=
:原始数据类型与对应包装器类型之间的相互赋值操作=
,会自动装箱(拆箱),前面已经描述过,此不赘述。- 注:原始数据类型赋值给
Long
,都需要明确标出是长整型,例如Long i = 1L;
,否则会报错(Type mismatch: cannot convert from int to Long
)。这也间接表明是默认转成 Integer 类型。。
- 注:原始数据类型赋值给
- 算术运算符
+
-
*
/
%
:包装器类型之间(或者包装器类型和原始数据类型)的算术运算操作+
-
*
/
%
- 注:
+
-
*
/
%
都是针对原始数据类型,参与运算的包装器类型都会自动拆箱。
- 注:
- 原始数据类型与包装器类型的大小比较
==
、< (<=)
、> (>=)
,会自动拆箱。- 对于
==
,如果都是包装器类型,则都是对象引用地址的比较,而<
(<=
) 、>
(>=
) 则始终要拆箱。
- 对于
- 三目运算符
? :
:当第二、第三位操作数分别为原始数据类型和包装器类型时,其中的包装器对象就会拆箱为原始数据类型进行操作。- 对于不同类型的包装器类型的三目运算符操作,则都是拆箱为原始数据类型进行比较,还可能存在原始数据类型提升。
- 注:对于表达式结果,可以直接使用
System.out.println()
进行打印,看看是使用了哪个重载的方法,进行侧面验证。
- 赋值操作
- 方法参数和方法返回值:自动装箱和拆箱
- 作为方法参数:
- 典型案例1:
包装器类型.equals(原始数据类型)
,原始数据类型会自动装箱。 例如:Integer.equals(1)
,1 会自动装箱为 Integer 包装器类型。 - 典型案例2:将原始数据类型放入集合类
- 如果方法参数接收的是原始数据类型,则传入包装器类型时会拆箱;如果方法参数接收的是包装器类型,则传入原始数据类型时会自动装箱。
- 典型案例1:
==
和equals
的区别(包装器类型和基本类型的比较)equals
使用的是对象进行比较。因此原始数据类型需要装箱。==
如果有原始数据类型,则包装器对象变量需要自动拆箱。
- 作为方法返回值
- 方法返回值是包装器类型:如果返回的是原始数据类型,会自动装箱
- 方法返回值是原始数据类型:如果返回的是包装器类型,会拆箱。
- 凡是涉及到拆箱的,都需要注意空指针问题。
- 作为方法参数:
/** * 自动装箱 和 拆箱 的常见场景 */ // 1、运算符:=、 +、 -、 *、 /、 %、== 、 < (<=) 、 > (>=)、? : int int256 = 256; Integer integer256 = 256; // 自动装箱 Integer integer255 = 255; // 自动装箱 boolean bl = true; // int 原始数据类型 System.out.println(int256); // Object --> Integer 包装器类型 System.out.println(integer256); // 拆箱:int 原始数据类型 // +、 -、 *、 /、 % 都是针对原始数据类型,都会自动拆箱 System.out.println(integer256 + int256); System.out.println(integer256 - int256); System.out.println(integer256 * int256); System.out.println(integer256 / int256); System.out.println(integer256 % int256); System.out.println(integer256 + integer256); System.out.println(bl ? integer256 : int256); System.out.println(bl ? int256 : integer256); // Object --> 都是相同的 包装器类型 ,则结果是 包装器类型 System.out.println(bl ? integer255 : integer256); Long Long255 = 255L; // 包装器类型不同,会拆箱和自动类型提升:long 原始数据类型 System.out.println(bl ? integer256 : Long255); System.out.println("===============2=============="); // 如果是对象比较,则为 false ,表明是原始数据类型自动装箱 // 如果是数值比较,则为 true ,表明是包装器类型自动拆箱 // integer256 拆箱 --> result : true System.out.println(integer256 == int256); // int256 自动装箱 --> result : true System.out.println(integer256.equals(int256)); // 默认 int 类型会自动装箱 Integer ,这里需明确标明为长整型, // 否则会报错:Type mismatch: cannot convert from int to Long Long Long256 = 256L; // 而原始数据类型则不会,原始数据类型可以自动进行类型提升 long kk = 256; // Long256 拆箱 --> result : true System.out.println(Long256 == kk); // Long256 拆箱 --> result : true System.out.println(Long256 == int256); // int256 自动装箱 Integer 包装器类型,不同类型比较返回 false // result : false System.out.println(Long256.equals(int256)); // result : false System.out.println(Long256.equals(integer256)); System.out.println("===============3.0=============="); // integer256 拆箱,int256 + int256 结果是 int 类型 Long Long512 = 512L; // int256 + int256 结果是 int 类型,原始数据类型提升而 Long512 自动拆箱 // result : true System.out.println(Long512 == (int256 + int256)); // int256 + int256 自动装箱为 Integer // result : false System.out.println(Long512.equals(int256 + int256)); System.out.println("===============3.1=============="); // 结果同上 System.out.println(Long512 == (int256 + integer256)); System.out.println(Long512.equals(int256 + integer256)); System.out.println("===============3.2=============="); // 结果同上 System.out.println(Long512 == (integer256 + integer256)); System.out.println(Long512.equals(integer256 + integer256)); System.out.println("===============3.3=============="); // int256 + Long256 自动装箱 Long System.out.println(Long512 == (int256 + Long256)); System.out.println(Long512.equals(int256 + Long256)); System.out.println("===============3.4=============="); // integer256 + Long256 自动装箱 Long System.out.println(Long512 == (integer256 + Long256)); System.out.println(Long512.equals(integer256 + Long256)); System.out.println("===============3.5==============");
3、相关问题及陷阱
以下是几个值得注意的问题:
- 1、缓存问题
- 从源码的实现来看,
Integer
本身的IntegerCache
默认缓存了[-128 ~ 127]
对应的包装器对象,我们在使用Integer.valueOf(int i)
时,只要对应的int
值最终落在[-128 ~ 127]
,则返回的都是已经缓存的包装器对象。 - 另一种常见的形式是
Integer i = 100;
,本质上就是调用Integer.valueOf(int i)
,而Integer i = new Integer (100);
则不会,它直接返回新创建的对象。 Byte
、Character
、Short
、Integer
、Long
都有相关的缓存,而Double
/Float
则没有。Boolean
则是内部的多例实例:TRUE
和FALSE
。具体可直接查看相关的JDK API源码。- 注:直接
new
的都是只有equals
才相等,而==
都不相等。 - 注:有兴趣的可以使用
javap -c MainClass
查看反编译的class文件,而IntegerCache
的具体实现,建议直接阅读参考JDK的源码。(后文也有贴上)
- 从源码的实现来看,
- 2、
Integer
等包装器类型的空指针问题- null 的
Integer
/Double
等包装器类型引用直接赋值给原始数据类会报异常java.lang.NullPointerException
。 - 因此, 在数据库DAO层的返回结果中,最好不要使用原始数据类型来接收结果字段,建议一直使用包装器类型来做接收处理,否则很容易出现因结果是 null 而导致的空指针异常。(常见的有 int 、double ,使用了
Integer.intValue()
/Double.doubleValue()
自动拆箱时报空指针异常) - 集合接收包装器类型/原始数据类型(自动装箱),在取值时如果使用原始数据类型来接收,需要特别注意空指针问题。
- 不同类型的包装器类型引用的三元运算符
?:
会拆箱,需要注意空指针问题,如 Integer 和 Long 。
- null 的
- 3、运算
- 当
==
运算符的两个操作数都是包装器类型引用,则比较指向的是否是同一个对象,而如果其中有一个操作数是原始数据类型(包含表达式结果)则比较的是数值(即会触发自动拆箱)。 - 不同类型的包装器不能
==
比较,报错提示:Incompatible operand types Long and Integer
- 当
- 4、大循环体中不建议频繁装拆箱
4、缓存上限的修改
以下是 Integer
源码中关于缓存内部静态类 IntegerCache
的相关源码及其文档说明,可以看到,有2种方式来修改缓存的上限(下限 -128 是固定的),如下:
- 通过JVM参数
-XX:AutoBoxCacheMax=<size>
来设置 - 通过系统属性
-Djava.lang.Integer.IntegerCache.high=<size>
来设置
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ 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() {} }
5、参考
- [1]5.1.7. Boxing Conversion https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7
- [2]5.1.8. Unboxing Conversion https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.8
- [3]Primitive Data Types https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
- [4]Autoboxing and Unboxing https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
- [5]How large is the Integer cache? https://stackoverflow.com/questions/15052216/how-large-is-the-integer-cache
- [6]示例可参考 https://github.com/wpbxin/java-learning-station/blob/master/java-basis-learning/src/main/java/cn/wpbxin/javabasis/datatype/PrimitiveDataTypesAndWrapperClass.java