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、参考

 

上一篇:002 mysql之中的存储引擎


下一篇:002.redis 持久化机对于生产环境中的灾难恢复的意义