自动装箱和拆箱的相关问题及陷阱
本篇将探讨自动装箱(Autoboxing)和拆箱(Unboxing)的相关概念、常见场景、可能涉及的问题及陷阱。目录结构如下:
- 1、自动装箱(Autoboxing)和拆箱(Unboxing)
- 2、自动拆箱和装箱的常见场景
- 3、相关问题及陷阱
- 4、缓存上限的修改
- 5、参考
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:将原始数据类型放入集合类
- 如果方法参数接收的是原始数据类型,则传入包装器类型时会拆箱;如果方法参数接收的是包装器类型,则传入原始数据类型时会自动装箱。
==
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
IntegerCache
的具体实现,建议直接阅读参考JDK的源码。(后文也有贴上)
- 2、
Integer
等包装器类型的空指针问题
- null 的
Integer
/Double
java.lang.NullPointerException
。 - 因此, 在数据库DAO层的返回结果中,最好不要使用原始数据类型来接收结果字段,建议一直使用包装器类型来做接收处理,否则很容易出现因结果是 null 而导致的空指针异常。(常见的有 int 、double ,使用了
Integer.intValue()
/Double.doubleValue()
自动拆箱时报空指针异常) - 集合接收包装器类型/原始数据类型(自动装箱),在取值时如果使用原始数据类型来接收,需要特别注意空指针问题。
- 不同类型的包装器类型引用的三元运算符
?:
会拆箱,需要注意空指针问题,如 Integer 和 Long 。
- 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