Java中的自动装箱与拆箱
1 自动装箱与拆箱
1.1 简单理解
自动装箱和拆箱从Java 1.5
开始引入,目的是将原始类型值自动地转换成对应的对象。自动装箱与拆箱的机制可以让我们在Java
的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接。
如果在Java1.5
下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合(Collections
)中放入原始类型值,因为集合只接收对象。通常这种情况下的做法是,将这些原始类型的值转换成对象,然后将这些转换的对象放入集合中。使用Integer,Double,Boolean
等这些类我们可以将原始类型值转换成对应的对象,但是从某些程度可能使得代码不是那么简洁精炼。为了让代码简练,Java 1.5
引入了具有在原始类型和对象类型自动转换的装箱和拆箱机制。但是自动装箱和拆箱并非完美,在使用时需要有一些注意事项,如果没有搞明白自动装箱和拆箱,可能会引起难以察觉的bug
。
1.2 什么是自动装箱和拆箱
自动装箱就是Java
自动将原始类型值转换成对应的对象,比如将int
的变量转换成Integer
对象,这个过程叫做装箱
,反之将Integer
对象转换成int
类型值,这个过程叫做拆箱
。
因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean
对应的封装类分别为Byte,Short,Character,Integer,Long,Float,Double,Boolean
1.3 自动装箱拆箱要点
自动装箱拆箱要点:
- 自动装箱时编译器调用
valueOf
将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue()
,doubleValue()
等这类的方法将对象转换成原始类型值。 - 自动装箱是将
boolean
值转换成Boolean
对象,byte
值转换成Byte
对象,char
转换成Character
对象,float
值转换成Float
对象,int
转换成Integer
,long
转换成Long
,short
转换成Short
,自动拆箱则是相反的操作。
1.4 何时发生自动装箱和拆箱
自动装箱和拆箱在Java
中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么Java
会自动将这个原始类型值转换成与之对应的对象。最经典的一个场景就是当我们向ArrayList
这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的ThreadLocal
ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1); //autoboxing - primitive to object
intList.add(2); //autoboxing
ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>();
intLocal.set(4); //autoboxing
int number = intList.get(0); // unboxing
int local = intLocal.get(); // unboxing in Java
举例说明:
上面的部分我们介绍了自动装箱和拆箱以及它们何时发生,我们知道了自动装箱主要发生在两种情况,一种是赋值
时,另一种是在方法调用
的时候。为了更好地理解这两种情况,我们举例进行说明。
1.4.1 赋值时
这是最常见的一种情况,在Java 1.5
以前需要手动地进行转换才行,而现在所有的转换都是由编译器来完成
//before autoboxing
Integer iObject = Integer.valueOf(3);
Int iPrimitive = iObject.intValue()
//after java5
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
1.4.2 方法调用时
这是另一个常用的情况,当在方法调用时,可以传入原始数据值或者对象,同样编译器会帮我们进行转换。
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
}
//autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer
show
方法接受Integer
对象作为参数,当调用show(3)
时,会将int
值转换成对应的Integer
对象,这就是所谓的自动装箱,show
方法返回Integer
对象,而int result = show(3);
中result
为int
类型,所以这时候发生自动拆箱操作,将show
方法的返回的Integer
对象转换成int
值。
1.5 自动装箱的弊端
自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。
Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}
上面的代码sum+=i
可以看成sum = sum + i
,但是+
这个操作符不适用于Integer
对象,首先sum
进行自动拆箱操作,进行数值相加操作,最后发生自动装箱操作转换成Integer
对象。其内部变化如下
sum = sum.intValue() + i;
Integer sum = new Integer(result);
由于我们这里声明的sum
为Integer
类型,在上面的循环中会创建将近4000
个无用的Integer
对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。
因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加GC
压力,拉低程序的性能。所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作
1.6 重载与自动装箱
当重载遇上自动装箱时,情况会比较有些复杂,可能会让人产生有些困惑。在1.5
之前,value(int)
和value(Integer)
是完全不相同的方法,开发者不会因为传入是int
还是Integer
调用哪个方法困惑,但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList
的remove
方法,它有remove(index)
和remove(Object)
两种重载,可能会有一点小小的困惑,其实这种困惑是可以验证并解开的,通过下面的例子我们可以看到,当出现这种情况时,不会发生自动装箱操作。
private void add(Integer a){
System.out.println("====");
};
private void add(int a){
System.out.println("------");
};
@Test
public void testOverride(){
DateDemo test = new DateDemo();
test.add(1);//------
test.add(Integer.valueOf(1));//====
}
重载时传入int a
和Integer a
是两个不同的方法,根据传入基本类型或者包装类型来判断走哪个方法
public class AutoboxingTest {
public static void main(String args[]) {
// Example 1: == comparison pure primitive – no autoboxing
int i1 = 1;
int i2 = 1;
System.out.println("i1==i2 : " + (i1 == i2)); // true
// Example 2: equality operator mixing object and primitive
Integer num1 = 1; // autoboxing
int num2 = 1;
System.out.println("num1 == num2 : " + (num1 == num2)); // true
// Example 3: special case - arises due to autoboxing in Java
Integer obj1 = 1; // autoboxing will call Integer.valueOf()
Integer obj2 = 1; // same call to Integer.valueOf() will return same
// cached Object
System.out.println("obj1 == obj2 : " + (obj1 == obj2)); // true
// Example 4: equality operator - pure object comparison
Integer one = new Integer(1); // no autoboxing
Integer anotherOne = new Integer(1);
System.out.println("one == anotherOne : " + (one == anotherOne)); // false
}
}
Output:
i1==i2 : true
num1 == num2 : true
obj1 == obj2 : true
one == anotherOne : false
值得注意的是第三个小例子,这是一种极端情况。obj1
和obj2
的初始化都发生了自动装箱操作。但是处于节省内存的考虑,JVM
会缓存-128
到127
的Integer
对象。因为obj1
和obj2
实际上是同一个对象。所以使用==
比较返回true
。
1.7 容易混乱的对象和原始数据值
另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当在一个原始数据值与一个对象进行比较时,如果这个对象没有进行初始化或者为Null
,在自动拆箱过程中obj.xxxValue
,会抛出NullPointerException
,如下面的代码
private static Integer count;
//NullPointerException on unboxing
if( count <= 0){
System.out.println("Count is not started yet");
}
1.8 缓存的对象
这个问题就是我们上面提到的极端情况,在Java
中,会对-128
到127
的Integer
对象进行缓存,当创建新的Integer
对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer
对象。
在Java
中另一个节省内存的例子就是字符串常量池,感兴趣的同学可以了解一下字符串详解
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了