自动装箱、拆箱了解多少?

写在前面

Java 为我们提供了 8 种基本数据类型,为什么还需要提供各自的包装类型呢?您可能会觉得这个问题问的很奇怪,但是我觉得还是值的思考的。

因为 Java 是一门面向对象的语言,基本数据类型并不具备对象的性质。而包装类型则是在基本类型的基础上,添加了属性和方法,从而成为了对象。试想,一个 int 类型怎么添加到 List 集合中?

如何理解Java中的自动拆箱和自动装箱?

自动拆箱?自动装箱?什么鬼,听都没听过啊,这...这..知识盲区...

回到家后赶紧查资料,我透,这不就是问基本类型跟封装类型吗,面试官整啥名词呢...

别问结果,问就是没过。

自动装箱、拆箱

定义: 基本数据类型和包装类之间可以自动地相互转换

理解: 装箱就是自动将基本数据类型转换为封装类型,拆箱就是自动将封装类型转换为基本数据类型。

我们来看个例子:

Integer number = 10; // 自动装箱
int count = number; // 自动拆箱

我们知道,Java 中提供了四大类基本数据类型,分别是:整数、浮点数、字符型和布尔型,其中:

  • 整数包含:byte、int、short、long
  • 浮点数包含:float、double
  • 字符类型:char
  • 布尔类型:boolean

基本数据类型相信大家一定很熟悉了吧,来来来,说说他们的取值范围~

数据类型 取值范围
byte -128 ~ 127
short -32786 ~ 32767
int -4294967296 ~ 4294967295
long -264 ~ 264 -1
float 3.4e-038 ~ 3.4e+038
dubbo 1.7e-308 ~ 1.7e+308
char \u0000 ~ \uffff
boolean true 、false

日常开发中,靠这些基本数据类型几乎能够满足我们的需求,但是基本类型终究不是对象,往重了说不满足面向对象的开发思想,往轻了说就是使用不方便。怎么讲?例如做一些数据类型转换,获取 int 数据类型的取值范围等等。

我们知道,类的优点在于它可以定义成员变量、成员方法,提供丰富便利的功能,因此 Java 在 JDK1.0 的时候就设计了基本数据类型的包装类,而在 JDK1.5 中引入了新特性:自动装箱和拆箱。

我们来看一下基本类型跟封装类型之间的对应关系:

数据类型 封装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

何时会发生拆箱、装箱?

自动装箱和拆箱在 Java 中很常见,比如我们有一个方法,接受一个对象类型的参数,如果我们传递一个原始类型值,那么 Java 会自动将这个原始类型值转换成与之对应的对象。最经典的一个场景就是当我们向 ArrayList 这样的容器中增加原始类型数据时或者是创建一个参数化的类,比如下面的 ThreadLocal。

ArrayList intList = new ArrayList();
intList.add(1); // 装箱
intList.add(2); // 装箱

ThreadLocal intLocal = new ThreadLocal();
intLocal.set(4); // 装箱

int number = intList.get(0); // 拆箱
int local = intLocal.get(); // 拆箱

上面的示例我们介绍了自动装箱和拆箱以及它们何时发生,我们知道了自动装箱主要发生在两种情况,一种是赋值时,另一种是在方法调用的时候。

赋值时

这是最常见的一种情况,在 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

方法调用时

这是另一个常用的情况,当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。

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 值。

自动装拆箱的弊端

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。

Integer sum = 0;
for(int i=1000; i<5000; i++){
   sum += i;
}

上面的代码 sum += i 可以看成 sum = sum + i,但是 + 这个操作符不适用于 Integer 对象,首先 sum 进行自动拆箱操作,然后进行数值相加操作,最后发生自动装箱操作转换成 Integer 对象。其内部变化如下:

int result = sum.intValue() + i;
Integer sum = new Integer(result);

由于我们这里声明的 sum 为 Integer 类型,在上面的循环中会创建将近 4000 个无用的 Integer 对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题。

总结

因为自动装箱会隐式地创建对象,像前面提到的那样,如果在一个循环体中,会创建无用的中间对象,这样会增加 GC 压力,降低程序的性能。所以在写循环时一定要注意,避免引入不必要的自动装箱操作。

总的来说,自动装箱和拆箱着实为开发者带来了很大的方便,但是在使用时也是需要格外留意,避免引起出现文章提到的问题。

你可能感兴趣的:(自动装箱、拆箱了解多少?)