浅谈Java自动拆装箱

什么是自动拆装箱

  • 装箱:将基本数据结构转化为包装器类型

    Integer i = 100;
    
  • 拆箱:将包装器类型转变为基本数据类型

    int ii = i;
    

基本数据类型的自动装箱、拆箱,是自J2SE 5.0开始提供的功能。打包数据类型虽然提供了方便,但是方便的同时表示隐藏了细节。

J2SE 5.0之前,包装基本数据类型,需要使用生成新的

Integer integer = new Integer(10);

J2SE 5.0之后,可以自动装箱:

Integer integer = 10;
需要拆装箱类型

这里写图片描述

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

这里写图片描述

自动拆装箱基本实现

  • 装箱:用包装器类型的valueOf()方法实现。

    下面为Integer的valueOf()方法的代码:

    public static Integer valueOf(int i) {
        // 判断i的大小,如果在[-128, 127]之间,则返回已在缓存中的Integer对象,否则
            if(i >= -128 && i <= IntegerCache.high)
                return IntegerCache.cache[i + 128];
            else
                return new Integer(i);
        }
    
    private static class IntegerCache {
            static final int high;
            static final Integer cache[];
    
            static {
                final int low = -128;
    
                // high value may be configured by property
                int h = 127;
                if (integerCacheHighPropValue != null) {
                    // Use Long.decode here to avoid invoking methods that
                    // require Integer's autoboxing cache to be initialized
                    int i = Long.decode(integerCacheHighPropValue).intValue();
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - -low);
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
            }
    
            private IntegerCache() {}
        }
    
    • valueOf()方法最终返回一个Integer对象。
    • 装箱的过程会创建对应的对象,消耗内存,影响性能。
  • 拆箱:用包装器的xxxValue()方法实现(其中xxx则是基本数据类型)

基本数据类型和包装器内存中的区别,如下图

img

基本数据类型是保存在栈中,但包装器类型的值则是保存在对内存中,对象保存在栈中,并指向其值。

自动拆装箱的好处

对现实世界的模拟

例如,表示年龄,当缺失此信息时,用null表示明显好于0,此使就需要使用包装器类型。

支持泛型

泛型必须是一个对象

提供丰富的api和属性

img

img

自动封装小问题

  1. 注意编译造成的运行时错误

    如下代码:

    Integer i = null;
    int j = i;
    

    上面代码编译时没有问题,但是运行时会有NullPointerException错误。因为编译器会将代码编译为一下语句:

    Integer i = null;
    int j = i.intValue();
    

    null表示没有任何对象实体,没有方法可以操作,因此抛出异常。

  2. 自动装箱时取值范围问题

    由于自动装箱时,调用包装类的valueOf()方法,有固定的取值范围,当使用==判断时,会导致引用地址是否相同的问题。

    public class Main {
         public static void main(String[] args) {
     
             Integer i1 = 100;
             Integer i2 = 100;
             Integer i3 = 200;
             Integer i4 = 200;
     
             System.out.println(i1==i2);  //true
             System.out.println(i3==i4);  //false
         }
     }
    

    上面代码便是个明显的例子:

    i1 == i2时,由于两个值是100,在[-128, 127]区间,valueOf()会在缓冲区寻找最大值,如果有,则会返回已存在的地址,因此是一样的;

    而 i3 == i4,值为200, 大于127,将直接创建新的Integer对象,因此两个地址是不一样的。

    但不是所有的包装类都是如此,在某个范围内的整型数值的个数是有限的,而浮点数却不是,因此浮点数每次封箱都是重新创建新对象。

    整型取值范围如下图:

    这里写图片描述

    此时,Boolean需要拿出来讨论,我们先看看Boolean的表现:

    public class Main {
         public static void main(String[] args) {
     
             Boolean i1 = false;
             Boolean i2 = false;
             Boolean i3 = true;
             Boolean i4 = true;
     
             System.out.println(i1==i2);//true
             System.out.println(i3==i4);//true
         }
     }
    

    此使,两种情况都是true,这是为什么呢?我们先看看Boolean的valueOf()方法:

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

    可以看见,Boolean已经提前创建好了两个对象,这样可以避免重复创建太多对象,因为它只有两个值。所以,Boolean封箱的时候都是同一个对象,所以地址都是相同的。

    PS:需要注意上面的情况是居于调用valueOf()方法的自动装箱,如果显式装箱则没有此问题

    Integer i9 = new Integer(100);
    Integer j9 = new Integer(100);
     System.out.println(i9 == j9);  //false
    
  3. 包装类对象和基本数据类型对比

    • ==

      Integer num1 = 400;  
      int num2 = 400;  
      System.out.println(num1 == num2); //true
      

      此时对包装类进行了拆箱,因此两者相同。

      对于==比较:

      • 如果两边都是同一包装器类型的引用,则比较是否指向同一个对象;
      • 如果有一边是算术表达式,则比较的是数值(包括类型不同),因为计算过程会出发自动拆箱
    • equals()方法

      Integer num1 = 100;  
      int num2 = 100;  
      Long num3 = 200l;  
      System.out.println(num1 + num2);  //200
      System.out.println(num3 == (num1 + num2));  //true
      System.out.println(num3.equals(num1 + num2));  //false
      

      为什么会出现上面的情况呢,我们先来看看Long的equals()方法的代码:

      public boolean equals(Object obj) {
              if (obj instanceof Long) {
                  return value == ((Long)obj).longValue();
              }
              return false;
          }
      

      从上面代码可以看出,equals()方法是需要先比较类型,再比较数值的。上面说了,计算表达式会进行拆箱,而equals()方法的参数为包装类,所以需要先进行封装,再比较

  4. Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别

    • 第一种方法不会自动封装,因此不存在valueOf()的过程
    • 第二种方式的执行效率和资源占用由于第一种,但并非绝对。

参考文章来源:

  1. 详解Java的自动装箱与拆箱(Autoboxing and unboxing)
  2. 深入剖析Java中的装箱和拆箱
  3. Java自动装箱/拆箱
  4. JAVA自动拆装箱详细说明

完结,撒花~
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值