开发易忽视的问题:Java拆箱和装箱源码实现

269 阅读4分钟

在Java中,自动装箱(Autoboxing)和自动拆箱(Unboxing)是编译器级别的功能,它们使得基本数据类型与其对应的包装类之间能够进行自动转换。这些特性极大地简化了代码,但是在底层上仍然依赖于明确的方法调用。以下是对自动装箱和自动拆箱在Java中的详细分析。

自动装箱(Autoboxing)

概念

自动装箱是指编译器自动将基本数据类型转换为其对应的包装类对象。例如,将int转换为Integer

实现原理

编译器会将基本数据类型转换为相应的包装类,通过调用包装类的静态方法valueOf()。例如:

int num = 10;
Integer boxedNum = num; // 自动装箱

编译后,会被转换为:

int num = 10;
Integer boxedNum = Integer.valueOf(num);

Integer.valueOf(int)源码分析

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

这里使用了一个内部类IntegerCache来缓存一部分常见的整数对象,以减少内存分配和提高性能。

IntegerCache的实现

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

    static {
        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);
                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() {}
}

在这个实现中,默认缓存范围是从-128127,这些数字会被缓存起来,以提高性能。当请求的值在此范围内时,会直接返回缓存中的对象,而不创建新的对象。

自动拆箱(Unboxing)

概念

自动拆箱是指编译器自动将包装类对象转换为基本数据类型。例如,将Integer转换为int

实现原理

编译器会将包装类对象转换为相应的基本数据类型,通过调用包装类的实例方法,例如intValue()。例如:

Integer boxedNum = new Integer(10);
int num = boxedNum; // 自动拆箱

编译后,会被转换为:

Integer boxedNum = new Integer(10);
int num = boxedNum.intValue();

Integer.intValue()源码分析

public int intValue() {
    return value;
}

intValue()方法只是返回封装在Integer对象内的基本类型的值。

详细示例分析

public class AutoboxingUnboxingExample {
    public static void main(String[] args) {
        // 自动装箱
        int num = 10;
        Integer boxedNum = num; // 实际上调用了 Integer.valueOf(num)
        
        // 自动拆箱
        Integer anotherBoxedNum = new Integer(20);
        int anotherNum = anotherBoxedNum; // 实际上调用了 anotherBoxedNum.intValue()

        // 测试缓存机制
        Integer cachedNum1 = Integer.valueOf(100);
        Integer cachedNum2 = Integer.valueOf(100);
        Integer nonCachedNum1 = Integer.valueOf(200);
        Integer nonCachedNum2 = Integer.valueOf(200);

        System.out.println("num: " + num);
        System.out.println("boxedNum: " + boxedNum);
        System.out.println("anotherNum: " + anotherNum);
        System.out.println("cachedNum1 == cachedNum2: " + (cachedNum1 == cachedNum2)); // 应该为 true
        System.out.println("nonCachedNum1 == nonCachedNum2: " + (nonCachedNum1 == nonCachedNum2)); // 应该为 false
    }
}

输出结果及其解释:

num: 10
boxedNum: 10
anotherNum: 20
cachedNum1 == cachedNum2: true
nonCachedNum1 == nonCachedNum2: false
  • cachedNum1 == cachedNum2为true:因为值为100的整数在缓存范围内(默认是-128到127),因此Integer.valueOf(100)返回的是同一个缓存对象。
  • nonCachedNum1 == nonCachedNum2为false:因为值为200的整数不在缓存范围内,每次调用Integer.valueOf(200)都会创建一个新的对象,因此它们的引用不同。

总结

  1. 自动装箱(Autoboxing)

    • 编译器将基本数据类型(如intfloat等)自动转换为相应的包装类(如IntegerFloat等)的对象。
    • 具体实现通过调用包装类的静态方法valueOf(),例如:Integer.valueOf(int)
  2. 自动拆箱(Unboxing)

    • 编译器将包装类对象自动转换为对应的基本数据类型。
    • 具体实现通过调用包装类的实例方法,如intValue()doubleValue()等。
  3. 缓存机制

    • 包装类(如IntegerLong等)通常会缓存常见的小范围值(如-128127),以提高性能并减少内存消耗。例如IntegerCache用于缓存Integer对象。
  4. 性能影响

    • 虽然自动装箱和拆箱提高了代码可读性,但频繁的装箱和拆箱操作可能带来额外的性能开销。
    • 在高性能要求的场景中,尽量避免不必要的装箱和拆箱操作。
  5. 空指针异常

    • 自动拆箱时,如果包装类对象为null,会引发NullPointerException
    • 在使用自动拆箱前,确保包装类对象不为null