在 Java 中,基本数据类型(如 intdoubleboolean 等)和对应的包装类型(如 IntegerDoubleBoolean 等)是两种不同的数据表示方式。基本数据类型是为了提高程序的运行效率而设计的,而包装类型则提供了更多的功能和灵活性。本文将详细探讨基本数据类型与包装类型之间的区别,以及它们之间的转换过程——拆箱与装箱。

Integer chenmo = new Integer(10); // 手动装箱
int wanger = chenmo.intValue();  // 手动拆箱

1 基本数据类型与包装类型的区别

  1. 包装类型可以为 null,而基本数据类型不可以

    包装类型可以为 null,这使得它们可以应用于 POJO(Plain Ordinary Java Object)中,而基本数据类型则不行。例如:

    class Writer {
        private Integer age;
        private String name;
    
        // Getter 和 Setter 方法
    }
    

    在这个例子中,age 字段使用了 Integer 包装类型,因此它可以为 null。如果使用基本数据类型 int,则无法表示 null 值。

  2. 包装类型可用于泛型,而基本数据类型不可以

    泛型不支持基本数据类型,因此在使用泛型时,必须使用包装类型。例如:

    List<Integer> list = new ArrayList<>(); // 正确
    List<int> list = new ArrayList<>(); // 编译错误
    

    这是因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本数据类型是个例外。

  3. 基本数据类型比包装类型更高效

    基本数据类型在栈中直接存储具体数值,而包装类型则存储堆中的引用。因此,基本数据类型在内存使用和性能上更高效。例如:

    int a = 100; // 基本数据类型
    Integer b = 100; // 包装类型
    

    基本数据类型 int 直接存储数值 100,而 Integer 对象则存储在堆中,并有一个引用指向它。

  4. 包装类型的值可以相同,但却不相等

    两个包装类型的值可以相同,但在使用 == 进行比较时,比较的是对象的引用,而不是值。例如:

    Integer chenmo = new Integer(10);
    Integer wanger = new Integer(10);
    
    System.out.println(chenmo == wanger); // false
    System.out.println(chenmo.equals(wanger)); // true
    

    在这个例子中,chenmowanger 的值都是 10,但它们是两个不同的对象,因此 == 比较结果为 false。而 equals 方法比较的是值,因此结果为 true

2 自动装箱与自动拆箱

Java 1.5 引入了自动装箱(Autoboxing)和自动拆箱(Unboxing)的功能,使得基本数据类型和包装类型之间的转换更加方便。

  • 自动装箱:将基本数据类型转换为对应的包装类型。
  • 自动拆箱:将包装类型转换为对应的基本数据类型。

例如:

Integer chenmo = 10; // 自动装箱
int wanger = chenmo; // 自动拆箱

反编译后的代码如下:

Integer chenmo = Integer.valueOf(10);
int wanger = chenmo.intValue();

可以看到,自动装箱是通过 Integer.valueOf() 完成的,而自动拆箱是通过 Integer.intValue() 完成的。

3 IntegerCache 缓存机制

在自动装箱过程中,Java 使用了一个缓存机制来优化性能。对于 Integer 类型,Java 在 -128127 之间的值进行了缓存。例如:

Integer c = 100;
Integer d = 100;
System.out.println(c == d); // true

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

在第一个例子中,cd 都指向缓存中的同一个 Integer 对象,因此 == 比较结果为 true。而在第二个例子中,200 不在缓存范围内,因此 cd 是两个不同的对象,== 比较结果为 false

自动装箱是通过 Integer.valueOf() 完成的,我们来看看这个方法的源码。

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

可以看到Integer的缓存类IntegerCache,来看一下 IntegerCache 的源码:

private static class IntegerCache {
    // 缓存的最小值,默认为 -128
    static final int low = -128;

    // 缓存的最大值,默认为 127,但可以通过 JVM 参数配置
    static final int high;
    static final Integer cache[];

    static {
        // 默认情况下 high 值为 127
        int h = 127;

        // 通过系统属性获取用户可能配置的更高的缓存上限
        // integerCacheHighPropValue 是一个字符串,代表配置的高值
        int i = parseInt(integerCacheHighPropValue);

        // 确保缓存的最高值至少为 127
        i = Math.max(i, 127);

        // 设置 high 的值,但不能超过 Integer.MAX_VALUE - (-low) - 1
        h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
        high = h;

        // 初始化缓存数组,大小为 high - low + 1
        cache = new Integer[(high - low) + 1];

        // 填充缓存,从 low 开始,为每个值创建一个 Integer 对象
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // 断言确保 high 的值至少为 127,这是 Java 语言规范要求的
        assert IntegerCache.high >= 127;
    }
}

-128127 之间的数会从 IntegerCache 中取,然后比较,所以第二段代码(100 在这个范围之内)的结果是 true,而第三段代码(200 不在这个范围之内,所以 new 出来了两个 Integer 对象)的结果是 false

结论:当需要进行自动装箱时,如果数字在 -128 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象。

4 自动拆箱的注意事项

自动拆箱虽然方便,但也可能引发性能问题。例如:

long t1 = System.currentTimeMillis();
Long sum = 0L;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i;
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);

在这个例子中,sum 被声明为 Long 类型,而 iint 类型。每次执行 sum += i 时,都会进行自动拆箱和自动装箱操作,导致性能下降。如果将 sum 声明为 long 类型,性能会显著提升。

5 总结

本文详细介绍了 Java 中基本数据类型与包装类型之间的区别,以及它们之间的转换过程——拆箱与装箱。通过理解这些概念,可以更好地利用 Java 的特性,编写更高效、更灵活的代码。希望本文能帮助你在 Java 编程的道路上更进一步。

6 思维导图

在这里插入图片描述

7 参考链接

深入浅出Java拆箱与装箱:理解自动类型转换与包装类的关系

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐