首页 > 技术文章 > 自动拆装箱

jry-blog 2018-08-22 16:04 原文

1.作用
2.概念定义
4.何时何地会自动拆装箱
5.可能带来的问题
参考资料

  

1.作用
  是一种java提供的语法糖,实际简化了基本数据类型与对应的对象包装类之间的转化.
  本身简化了基本数据类型在对象使用时的代码.
  通过自动拆装箱实现了泛型对基本数据类型的兼容.
  实际自动拆装箱的实现,有效提高了效率,减少了对象的创建.(享元模式:常用对象缓存)

2.概念定义
  首先得明白什么是拆装箱.
  装箱:指将基本类型转化为对应的包装类对象,比如int到Integer,float到Float.

    一般将基本类型作为参数传入包装类构造方法.
  拆箱:与装箱相反,由对应包装类对象转为基本数据类型.

    使用包装类的方法获取内部封装的基本类型值.
  而自动拆装箱大大简化了这一过程,通过简单的赋值操作,便可以实现两者之间对应的直接的转换.
  Integer integer = 10; int i = integer;

  基本数据类型与对应的包装类如下:

Primitive  type  Wrapper class
boolean  Boolean
byte Byte
char  Character
float Float
int  Integer
long  Long
short Short
double  Double 


    

 

 

 

 

 

 

3.如何实现
  装箱:实际调用包装器的valueOf()方法;
  拆箱:实际使用包装器的xxxValue()方法.

1 Integer integer = 10;
2 int i = integer;
3 编译结果如下:
4 Integer integer = Integer.valueOf((int)10);
5 int i = integer.intValue();

  valueOf()的方法实现解释了为什么自动装箱比手动通过构造器装箱的效率更高了.

1 public static Integer valueOf(int i) {
2 //但基本数据类型值 在指定范围之内时,会返回原先缓存的对象,默认范围为-128到127
3     if (i >= IntegerCache.low && i <= IntegerCache.high)
4         return IntegerCache.cache[i + (-IntegerCache.low)];
5     return new Integer(i);
6 }

 

  由于Integer比较特殊,可以设置缓存范围,因此以Integer为例,看一下缓存的实现

 1    static final int low = -128;//不可被修改
 2     static final int high;
 3 
 4     static {
 5   // 默认缓存范围是从-128 到 127
 6   //但是最大值允许配置
 7         int h = 127;
 8         String integerCacheHighPropValue =
 9             sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
10         if (integerCacheHighPropValue != null) {
11             try {
12                 int i = parseInt(integerCacheHighPropValue);
13 //设置的缓存值与默认值比较,取较大的一个
14                 i = Math.max(i, 127);
15                 // Maximum array size is Integer.MAX_VALUE:不能超过数组的最大长度
16                 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
17             } catch( NumberFormatException nfe) {
18                 // If the property cannot be parsed into an int, ignore it.
19             }
20         }
21         high = h;
22 
23         cache = new Integer[(high - low) + 1];
24         int j = low;
25         for(int k = 0; k < cache.length; k++)
26             cache[k] = new Integer(j++);
27 
28         // range [-128, 127] must be interned (JLS7 5.1.7)
29         assert IntegerCache.high >= 127;
30

  设置缓存最大值:
  运行时可通过-XX:AutoBoxCacheMax=1000 指定最大缓存

  

  除去Double与Float类型外(存在浮点数,没有经常使用的热点值),Character/Byte/Short/Long/Boolean都有各自的缓存实现.不过相对于Integer,只能使用默认的缓存范围.
    数值型的范围为-128到127:Byte/Short/Long
    Character字符型:0到127
    Boolean:只有true和false 的实现

public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

 

4.何时何地会自动拆装箱
  我目前想到的,一般情况下,在进行数值赋值,与数值比较,运算符计算或涉及到基本包装类的泛型时,会触发自动拆装箱.

  涉及基本类型赋值运算:

public static void main(String[] args) {
    int i1 = 1;
    Integer integer1 = 1;
    Integer integer2 = 2;
    System.out.println(i1 == integer1);
    System.out.println(integer1 == integer2);
    System.out.println(integer1.equals(integer1));
    System.out.println(integer1.equals(1));
    System.out.println(integer1 + i1);
    System.out.println(integer1 + integer2);
}

//结果
true
false
true
true
2
3

   反编译结果如下:

public static void main(String[] args) {
        int i1 = 1;
        Integer integer1 = Integer.valueOf((int)1);//赋值时的装箱
        Integer integer2 = Integer.valueOf((int)2);
        System.out.println((boolean)(i1 == integer1.intValue()));//与数值比较时的拆箱
        System.out.println((boolean)(integer1 == integer2));//包装类之间依旧是比较地址
        System.out.println((boolean)integer1.equals((Object)integer1));
        System.out.println((boolean)integer1.equals((Object)Integer.valueOf((int)1)));//equals接受的是对象,因此只有可能触发自动装箱
        System.out.println((int)(integer1.intValue() + i1));//进行数值计算,会转换为基本数据类型计算
        System.out.println((int)(integer1.intValue() + integer2.intValue()));
    }

   泛型相关的使用:

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>(6);
    list.add(1);
//通过自动拆装箱,实际上使得泛型机制能够支持基本数据类型的操作.极大简化了代码
//list.add((Integer)Integer.valueOf((int)1));
}

  实际上在项目中的应用,基于我有限经验的理解,在接收外部数据或者数据库数据,映射到类的属性中或者复制到变量中时,推荐使用包装类.

  因为数据返回有可能会是null,如果直接转换成基本类型,会导致报错.在这转换过程中,自动拆装箱的使用将有效提高效率,减少null的判断.

5.可能带来的问题
空指针:
  就像上面说的,自动拆箱很容易导致空指针异常,尤其是在允许存入null的集合中取得数据时.因此,在使用包装类进行可能触发自动拆箱的操作时候,务必要注意处理null值的情况,防止空指针异常.

内存问题:
  注意自动装箱会创建对象的过程.很有可能在你以为只是简单使用基本类型时,却导致自动装箱在大量创建对象.
  例如不知道赋值变量的类型,使用超过缓存的数值进行大量赋值,导致自动装箱创建对象.你所预想使用基本类型在退出方法后,就被清除了.但实际上大量创建的对象被保存在堆上,导致内存空间被大量占用,降低了系统的性能.

 

参考资料:

链接:Autoboxing and Unboxing (The Java Tutorials > Lea...

链接:深入剖析Java中的装箱和拆箱 - 海 子 - 博客园

链接:Java 自动装箱与拆箱的实现原理 - 简书

https://blog.csdn.net/qq_38071004/article/details/81911165

 

推荐阅读