zoukankan      html  css  js  c++  java
  • Java基础(002):自动装箱和拆箱的相关问题及陷阱

      本篇将探讨自动装箱(Autoboxing)和拆箱(Unboxing)的相关概念、常见场景、可能涉及的问题及陷阱。目录结构如下:

    1、自动装箱(Autoboxing)和拆箱(Unboxing)

      自动装箱和拆箱的概念如下:

    • 自动装箱:直接将一个原始数据类型传给其相应的包装器类型(wrapper class),编译器会自动转换成对应的包装器类型,这就是自动装箱(Autoboxing)。
    • 拆箱:将一个包装器类型的对象赋值给其相应的原始数据类型变量,编译器会自动转换成对应的原始数据类型,则称为拆箱(Unboxing)。
    • 由此可见,拆箱与装箱是相反的操作,而且都是编译器添加的自动处理。

      示例如下:

    // 自动装箱 Autoboxing
    // Xxx.valueOf(xxx i)
    // 编译器会转成 Integer.valueOf(int i) 进行自动装箱
    Integer i = 100;
    Integer j = Integer.valueOf(100);
    System.out.println("i == j --> " + (i == j));
    j = Integer.valueOf(i);
    System.out.println("i == j --> " + (i == j));
    // 直接创建包装器类型的对象
    Integer k = new Integer(100);
    System.out.println("i == k --> " + (i == k));
    
    // 拆箱 Unboxing
    // Xxx..xxxValue()
    // 编译器会转成 Integer.intValue() 进行拆箱
    int m = i;
    System.out.println("i == m --> " + (i == m));

    2、自动拆箱和装箱的常见场景

       这里主要涉及到到几类常见场景:运算符操作、方法参数和方法返回值。这些都是比较常见的存在自动拆箱和装箱过程的场景。详细说明如下:

    • 运算符操作
      • 赋值操作 = :原始数据类型与对应包装器类型之间的相互赋值操作 = ,会自动装箱(拆箱),前面已经描述过,此不赘述。
        • 注:原始数据类型赋值给 Long ,都需要明确标出是长整型,例如 Long i = 1L; ,否则会报错(Type mismatch: cannot convert from int to Long)。这也间接表明是默认转成 Integer 类型。。
      • 算术运算符 + - * / % :包装器类型之间(或者包装器类型和原始数据类型)的算术运算操作 +- * / %
        • 注:+ - * / % 都是针对原始数据类型,参与运算的包装器类型都会自动拆箱。
      • 原始数据类型与包装器类型的大小比较 == 、 < (<=) 、 > (>=),会自动拆箱。
        • 对于 == ,如果都是包装器类型,则都是对象引用地址的比较,而< (<=) 、 > (>=) 则始终要拆箱。
      • 三目运算符 ? : :当第二、第三位操作数分别为原始数据类型和包装器类型时,其中的包装器对象就会拆箱为原始数据类型进行操作。
        • 对于不同类型的包装器类型的三目运算符操作,则都是拆箱为原始数据类型进行比较,还可能存在原始数据类型提升。
      • 注:对于表达式结果,可以直接使用 System.out.println() 进行打印,看看是使用了哪个重载的方法,进行侧面验证。
    • 方法参数和方法返回值:自动装箱和拆箱
      • 作为方法参数:
        • 典型案例1:包装器类型.equals(原始数据类型),原始数据类型会自动装箱。 例如: Integer.equals(1) ,1 会自动装箱为 Integer 包装器类型。
        • 典型案例2:将原始数据类型放入集合类
        • 如果方法参数接收的是原始数据类型,则传入包装器类型时会拆箱;如果方法参数接收的是包装器类型,则传入原始数据类型时会自动装箱。
      • == 和 equals 的区别(包装器类型和基本类型的比较)
        • equals 使用的是对象进行比较。因此原始数据类型需要装箱。
        • == 如果有原始数据类型,则包装器对象变量需要自动拆箱。
      • 作为方法返回值
        • 方法返回值是包装器类型:如果返回的是原始数据类型,会自动装箱
        • 方法返回值是原始数据类型:如果返回的是包装器类型,会拆箱。
      • 凡是涉及到拆箱的,都需要注意空指针问题。
    /**
     * 自动装箱 和 拆箱 的常见场景
     */
    
    // 1、运算符:=、 +、 -、 *、 /、 %、== 、 < (<=) 、 > (>=)、? :
    int int256 = 256;
    Integer integer256 = 256;    // 自动装箱
    Integer integer255 = 255;    // 自动装箱
    boolean bl = true;
    // int 原始数据类型
    System.out.println(int256);
    // Object --> Integer 包装器类型 
    System.out.println(integer256);
    // 拆箱:int 原始数据类型
    // +、 -、 *、 /、 % 都是针对原始数据类型,都会自动拆箱
    System.out.println(integer256 + int256);
    System.out.println(integer256 - int256);
    System.out.println(integer256 * int256);
    System.out.println(integer256 / int256);
    System.out.println(integer256 % int256);
    System.out.println(integer256 + integer256);
    System.out.println(bl ? integer256 : int256);
    System.out.println(bl ? int256 : integer256);
    // Object --> 都是相同的 包装器类型 ,则结果是 包装器类型 
    System.out.println(bl ? integer255 : integer256);
    Long Long255 = 255L;
    // 包装器类型不同,会拆箱和自动类型提升:long 原始数据类型
    System.out.println(bl ? integer256 : Long255);
    System.out.println("===============2==============");
    
    // 如果是对象比较,则为 false ,表明是原始数据类型自动装箱
    // 如果是数值比较,则为 true ,表明是包装器类型自动拆箱
    // integer256 拆箱 --> result : true
    System.out.println(integer256 == int256);
    
    // int256 自动装箱 --> result : true
    System.out.println(integer256.equals(int256));
    
    // 默认 int 类型会自动装箱 Integer ,这里需明确标明为长整型,
    // 否则会报错:Type mismatch: cannot convert from int to Long
    Long Long256 = 256L;
    // 而原始数据类型则不会,原始数据类型可以自动进行类型提升
    long kk = 256;
    // Long256 拆箱 --> result : true
    System.out.println(Long256 == kk);
    
    // Long256 拆箱 --> result : true
    System.out.println(Long256 == int256);
    
    // int256 自动装箱 Integer 包装器类型,不同类型比较返回 false
    // result : false
    System.out.println(Long256.equals(int256));
    // result : false
    System.out.println(Long256.equals(integer256));
    System.out.println("===============3.0==============");
    
    // integer256 拆箱,int256 + int256 结果是 int 类型
    Long Long512 = 512L;
    // int256 + int256 结果是 int 类型,原始数据类型提升而 Long512 自动拆箱
    // result : true
    System.out.println(Long512 == (int256 + int256));
    // int256 + int256 自动装箱为 Integer
    // result : false
    System.out.println(Long512.equals(int256 + int256));
    System.out.println("===============3.1==============");
    
    // 结果同上
    System.out.println(Long512 == (int256 + integer256));
    System.out.println(Long512.equals(int256 + integer256));
    System.out.println("===============3.2==============");
    
    // 结果同上
    System.out.println(Long512 == (integer256 + integer256));
    System.out.println(Long512.equals(integer256 + integer256));
    System.out.println("===============3.3==============");
    
    // int256 + Long256 自动装箱 Long
    System.out.println(Long512 == (int256 + Long256));
    System.out.println(Long512.equals(int256 + Long256));
    System.out.println("===============3.4==============");
    
    // integer256 + Long256 自动装箱 Long
    System.out.println(Long512 == (integer256 + Long256));
    System.out.println(Long512.equals(integer256 + Long256));
    System.out.println("===============3.5==============");

      

    3、相关问题及陷阱

    以下是几个值得注意的问题:

    • 1、缓存问题
      • 从源码的实现来看, Integer 本身的 IntegerCache 默认缓存了 [-128 ~ 127] 对应的包装器对象,我们在使用 Integer.valueOf(int i) 时,只要对应的 int 值最终落在[-128 ~ 127],则返回的都是已经缓存的包装器对象
      • 另一种常见的形式是 Integer i = 100; ,本质上就是调用 Integer.valueOf(int i) ,而 Integer i = new Integer (100); 则不会,它直接返回新创建的对象。
      • ByteCharacterShortIntegerLong都有相关的缓存,而Double/Float则没有。Boolean 则是内部的多例实例:TRUE 和 FALSE。具体可直接查看相关的JDK API源码。
      • 注:直接 new 的都是只有 equals 才相等,而 == 都不相等。
      • 注:有兴趣的可以使用 javap -c MainClass 查看反编译的class文件,而 IntegerCache 的具体实现,建议直接阅读参考JDK的源码。(后文也有贴上)
    • 2、Integer 等包装器类型的空指针问题
      • null 的 Integer/Double 等包装器类型引用直接赋值给原始数据类会报异常 java.lang.NullPointerException
      • 因此, 在数据库DAO层的返回结果中,最好不要使用原始数据类型来接收结果字段,建议一直使用包装器类型来做接收处理,否则很容易出现因结果是 null 而导致的空指针异常。(常见的有 int 、double ,使用了 Integer.intValue()/Double.doubleValue() 自动拆箱时报空指针异常)
      • 集合接收包装器类型/原始数据类型(自动装箱),在取值时如果使用原始数据类型来接收,需要特别注意空指针问题
      • 不同类型的包装器类型引用的三元运算符 ?: 会拆箱,需要注意空指针问题,如 Integer 和 Long
    • 3、运算
      •  == 运算符的两个操作数都是包装器类型引用,则比较指向的是否是同一个对象,而如果其中有一个操作数是原始数据类型(包含表达式结果)则比较的是数值(即会触发自动拆箱)。
      • 不同类型的包装器不能 == 比较,报错提示:Incompatible operand types Long and Integer
    • 4、大循环体中不建议频繁装拆箱

    4、缓存上限的修改

      以下是 Integer 源码中关于缓存内部静态类 IntegerCache 的相关源码及其文档说明,可以看到,有2种方式来修改缓存的上限(下限 -128 是固定的),如下:

    • 通过JVM参数 -XX:AutoBoxCacheMax=<size> 来设置
    • 通过系统属性 -Djava.lang.Integer.IntegerCache.high=<size> 来设置 
    /**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
    
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
    
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
    
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
    
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
    
        private IntegerCache() {}
    }

      

    5、参考

  • 相关阅读:
    Vuex
    浏览器渲染页过程描述
    mvvm 模式
    flex 布局
    js 浮点数计算
    3、异步编程-JS种事件队列的优先级
    高阶函数 debounce 和 throttle
    记录学习MVC过程,HTML铺助类(二)
    记录学习MVC过程,控制器方法和视图(一)
    修改以前项目遇到,所有页面继承BaseBage,Sesssion保存一个model,实现登录(记录一下)
  • 原文地址:https://www.cnblogs.com/wpbxin/p/14311010.html
Copyright © 2011-2022 走看看