Java 必知必会——一文详解对象包装器和自动装箱与拆箱

1. 对象包装器

某些情况下,基本类型如 int 需要转换为对象。

因此,在 Java 中,所有的基本类型都有一个与之对应的类,通常,这些类被称为包装器。

对象包装器

对象包装器类是不可变的,也就是说,一旦构造了包装器,就不允许改变包装在其中的值。

同时包装器类还是 final ,因此,不能定义包装器类的字类。

注意:由于包装器类引用可以为 null, 因此自动装箱可能会抛出 NullPointException 异常:

Integer n = null;
System.out.println(2 * n); // Throws NullPointerException

2. 自动装箱与拆箱

2.1 什么是自动装箱与拆箱?

自动装箱:将一个基本类型赋值给一个对应的包装器对象。

Integer number = 1;

编译器会自动将上述语句转换为:

Integer number = Integer.valueOf(1);

自动拆箱:将一个包装器对象付给一个对应的基本类型。

int n = number;

编译器会自动将上述语句转换为:

int n = number.intValue();

在算术表达式中,出现基本数据类型与包装器对象进行运算时,编译器会自动插入对象拆箱的指令,对基础数据类型计算后,再将结果装箱。

此时,表达式中出现混用 IntegerDouble 类型,Integer 值会拆箱,提升为 Double,再封箱为 Double

Integer n = 1;
Double d = 2.0;
System.out.println(true ? n : d); // 1.0

注意

  1. 装箱和拆箱是编译器认可的,而不是虚拟机。编译器在生成类的字节码时,会插入必要的方法调用。虚拟机只是执行这些字节码。

  2. 自动拆箱陷阱:注意包装器对象是否为 null。

    Integer i = null;
    int n = i; // Throws NullPointerException
    

    由于 i 为对象,可以指向 null,但运行时,会对 i 进行拆箱,也就是对一个空对象执行 intValue() 方法,因此会抛出空指针异常。

2.2 比较问题
2.1 包装器对象之间的比较

基于对象对比机制可知,== 检测的实际是对象是否指向同一块存储区域,因此,不同对象之间的比较一般不相等。

而包装器类的特殊实现,会导致不同对象指向同一块存储区域,从而出现以下情况。

Integer a = 1;
Integer b = 1; 
System.out.println(a == b); // true

Integer c = 1000;
Integer d = 1000;
System.out.println(c == d); // false

为什么会出现这种情况,根据自动装箱可知

Integer a = 1;

实际执行了

Integer a = Integer.valueOf(1);

查看 Integer 源码:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

根据源码发现,传入的 int 值 在一定范围内是不会自动创建的,而是一个 IntegerCache 类的 cache 数组属性,因此,在一定范围内,Integer 包装器的对象返回的是同一个 Integer 对象,此时,== 成立。

查看 IntegerCache 源码:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;
    static Integer[] archivedCache;

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                h = Math.max(parseInt(integerCacheHighPropValue), 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        // Load IntegerCache.archivedCache from archive, if possible
        VM.initializeFromArchive(IntegerCache.class);
        int size = (high - low) + 1;

        // Use the archived cache if it exists and is large enough
        if (archivedCache == null || size > archivedCache.length) {
            Integer[] c = new Integer[size];
            int j = low;
            for(int i = 0; i < c.length; i++) {
                c[i] = new Integer(j++);
            }
            archivedCache = c;
        }
        cache = archivedCache;
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

根据源码可知,值为-128 ~ 127Integer 包装器对象引用的是同一缓存数字中的对象,因此,它们是相等的。

其他的包装器类也有类似的缓存操作,为了避免多次创建对象,事先创造好了一个缓存数组,如果值在这个范围内,就会直接返回实现创建好的对象。

但是,某个范围内的整型数值是有限的,而浮点数不是。

因此, DoubleFloat 类型就没有类似的缓存数组。

类型相同对象范围不同对象范围
Integer-128 ~ 127i >= 128 || i < -128
Short-128 ~ 127s >=128 || s < -128
Character< 128c > 128
Long-128 ~ 127l >= 128 || i < -128
Byte全部
Boolean全部
Double全部
Float全部
2.2 基础类型与包装器类的比较

== 比较运算符:

  • 当两个操作数都是包装器类的对象时,比较的是否为同一个对象。
  • 当其中一个是表达式时,则比较的是数值(会触发自动拆箱)。
Integer a = 100;
Integer b = 200;
Long c = 300l;
Long d = 200l;
System.out.println(c == (a+b)); // true
System.out.println(c.equals(a+b)); // false
System.out.println(c.equals(a+d)); // true

第一次,先触发自动拆箱,调用 intValue() 方法,比较数值是否相等,因此,结果为true

第二次,先触发自动拆箱,调用 intValue() 方法,再进行自动装箱,由于两个都为Integer 类型,自动装箱为 Integer.valueof() ,进行 equals() 比较时,类型不相等,因此,结果为 false

第三次,先触发自动拆箱,调用 intValue() 方法,再进行自动装箱,由于其中一个为 Long 类型,自动装箱变为 Long.valueof() ,进行 equals() 比较时,类型相等,数值相等,因此,结果为 true

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值