Java中的自动装箱与拆箱

技术文档网 2021-04-23
引言

自动装箱和拆箱是从Java 1.5开始引入的,目的是将原始类型值自动转换为对应的对象。自动装箱和拆箱机制可以 让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或对象类型更加简单直接。

什么是自动装箱与拆箱

自动装箱是Java自动将原始类型值转换为对应的对象,比如将int的变量转换为Integer对象,这个过程为“装箱”, 反之将Integer对象转换成int类型值,称为“拆箱”。

自动装箱拆箱的关键
  • 自动装箱时编译器调用valueOf将原始类型值转换为对象,同时拆箱时,编译器调用类似intValue()、doubleValue() 这类方法将对象转换为原始类型值。
  • 常见原始类型值与对象对应:boolean(Boolean)、byte(Byte)、char(Character)、float(Float)、int(Integer) long(Long)、short(Short)等。
何时会发生自动装箱拆箱
  • 赋值时:在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
自动装箱的弊端:创建多余的对象,影响性能。
  • 循环迭代
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对象,在这样庞大的循环中, 会降低程序的性能并且加重了垃圾回收的工作量。因此在我们编程时,需要注意到这一点,正确地声明变量类型,避免 因为自动装箱引起的性能问题。

  • 集合与数组

假设我们要定义一个整型数组列表,那么我们应该选择:ArrayList list 还是 int[] arr 呢?

由于ArrayList中每个值分别包装在对象里中,所以ArrayList的效率远远低于int[]数组,不过对于小型集合, 我们依旧可以选择ArrayList,因为其方便性要比效率性更重要。

重载与自动装箱

在1.5之前,value(int)和value(Integer)是完全不相同的方法,开发者不会因为传入是int还是Integer调用哪个方法困惑, 但是由于自动装箱和拆箱的引入,处理重载方法时稍微有点复杂。一个典型的例子就是ArrayList的remove方法,它有remove(index) 和remove(Object)两种重载。

public void test(int num){
    System.out.println("method with primitive argument");

}

public void test(Integer num){
    System.out.println("method with wrapper argument");

}

//calling overloaded method
AutoboxingTest autoTest = new AutoboxingTest();
int value = 3;
autoTest.test(value); //no autoboxing
Integer iValue = value;
autoTest.test(iValue); //no autoboxing

Output:
method with primitive argument
method with wrapper argument

注意事项

  • 对象相等比较:
//在-128~127 之外的数
 Integer i1 =200;
 Integer i2 =200;
 System.out.println("i1==i2: "+(i1==i2));
 // 在-128~127 之内的数
 Integer i3 =100;
 Integer i4 =100;
 System.out.println("i3==i4: "+(i3==i4));

输出结果:
i1==i2: false
i3==i4: true

我们知道“==”比较的是两个对象的引用地址是否相同,也是用来比较两个基本类型数据的变量值是否相同。 当我们给Integer对象赋值时,编译器执行了自动装箱,调用了Integer.valueOf(int i)方法,如下:

public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)  // 没有设置的话,IngegerCache.high 默认是127
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}

如上,我们可以看到在-128~127之间的值,其返回的时缓存的Integer对象的值,因此指向的是同一个地址,而 不在此范围的,会创建新的Integer对象。

容易混乱的对象和原始数据值

另一个需要避免的问题就是混乱使用对象和原始数据值,一个具体的例子就是当我们在一个原始数据值与一个对象 进行比较时,如果这个对象没有进行初始化或者为Null,在自动拆箱过程中obj.xxxValue,会抛出 NullPointerException,如下面的代码:

private static Integer count;

//NullPointerException on unboxing
if( count <= 0){
  System.out.println("Count is not started yet");
}

相关文章

  1. 基于-SLF4J-MDC-机制的日志链路追踪配置属性

    ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC

  2. ajax-跨域访问

    ajax 跨域访问 &lt;!DOCTYPE html&gt; &lt;html xmlns:th="http://www.w3.org/1999/xhtml"&gt; &lt;head&gt;

  3. 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache

    spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port

  4. Java动态代理

    Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public

  5. Java读取classpath中的文件

    public void init() { try { //URL url = Thread.currentThread().getContextClassLo

随机推荐

  1. 基于-SLF4J-MDC-机制的日志链路追踪配置属性

    ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC

  2. ajax-跨域访问

    ajax 跨域访问 &lt;!DOCTYPE html&gt; &lt;html xmlns:th="http://www.w3.org/1999/xhtml"&gt; &lt;head&gt;

  3. 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache

    spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port

  4. Java动态代理

    Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public

  5. Java读取classpath中的文件

    public void init() { try { //URL url = Thread.currentThread().getContextClassLo