深入理解Java包装类:自动装箱拆箱与缓存池机制

发布于:2025-04-21 ⋅ 阅读:(10) ⋅ 点赞:(0)

深入理解Java包装类:自动装箱拆箱与缓存池机制

对象包装器

Java中的数据类型可以分为两类:基本类型引用类型。作为一门面向对象编程语言, 一切皆对象是Java语言的设计理念之一。但基本类型不是对象,无法直接参与面向对象操作,为了解决这个问题,Java让每个基本类型都有一个与之对应的包装器类型。

Java中有8种不可变的基本类型,分别为:

  • 整型byteshortintlong
  • 浮点类型floatdouble
  • 字符类型char
  • 布尔类型boolean
类型 大小 默认值 示例
byte 1字节 0 byte b = 10
short 2字节 0 short s = 200
int 4字节 0 int i = 1000
long 8字节 0L long l = 5000L
float 4字节 0.0f float f = 3.14f
double 8字节 0.0d double d = 2.718
char 2字节 ‘\u0000’ char c = 'A'
boolean 未明确定义 false boolean flag = true

这八种基本类型都有对应的包装类分别为:ByteShortIntegerLongFloatDoubleCharacterBoolean (前6个派生于公共的超类Number)。包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是final,因此不能派生它们的子类。

Java不是“一切皆对象”吗,为什么还要保留基本数据类型?

包装类是引用类型,对象的引用存储在栈中,对象本身存储在堆中;而对于基本数据类型,变量对应的内存块直接存储数据本身(栈中)。因此,基本数据类型读写效率更高效。在64位JVM上,在开启引用压缩的情况下,一个Integer对象占用16个字节的内存空间,而一个int类型数据只占用4字节的内存空间,前者对空间的占用是后者的4倍。也就是说,不管是读写效率还是存储效率,基本类型都更高效。尽管Java强调面向对象,但为了性能做了妥协。

自动装箱与拆箱

装箱拆箱是实现基本数据类型与包装类之间相互转换的特性。Java 5引入自动装箱/拆箱功能,进一步简化了包装类的使用。

  • 装箱:将基本数据类型转化为对应的包装类对象。
  • 拆箱:将包装类对象转化为对应的基本数据类型值。

示例

Integer a = 100; // 自动装箱 -> Integer.valueOf(100)

int b = a; // 自动拆箱 -> a.intValue()

// 自动装箱和拆箱也适用于算术表达式
Integer n = 3;
n++; // 编译器将自动插入一条对象拆箱的指令,然后进行自增运算,最后再将结果装箱

装箱其实就是调用了包装类的valueOf()方法,拆箱其实就是调用了 xxxValue()方法。

API java.lang.Integer

  • int intValue()

    将这个Integer对象的值作为一个int返回(覆盖Number类中的intValue方法)。

  • static Integer valueOf(String s)

    返回一个新的Integer对象,用字符串s表示的整数初始化。指定字符串必须表示一个十进制整数。

关于自动装箱还有几点需要注意:

  • 高频装箱拆箱(如循环)会产生大量临时对象,消耗内存和GC资源:

    // 错误示例:每次循环触发装箱
    Long sum = 0L;
    for (long i = 0; i < 1e6; i++) {
        sum += i; // sum = Long.valueOf(sum.longValue() + i)
    }
    
    // 正确优化:使用基本类型
    long sum = 0L;
    for (long i = 0; i < 1e6; i++) {
        sum += i;
    }
    
  • 由于包装器类引用可以为null,所以自动装箱有可能会抛出一个NullPointerException异常:

    Integer n = null;
    System.out.println(2 * n) // throws NullPointerException
    
  • 如果在一个表达式中混合使用IntegerDouble类型,Integer值就会拆箱,提升为double,再装箱为Double

    Integer n = 1;
    Double x = 2.0;
    System.out.println(true ? n : x); // 1.0
    
  • 装箱和拆箱是编译器要做的工作,而不是虚拟机。编译器在生成类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码。

缓存池机制

缓存池是 Java 为优化包装类对象创建和内存消耗而设计的核心机制,通过预创建和复用常用数值的包装类对象,减少重复对象创建的开销。Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 TRUE or FALSE

示例

Integer a = 100; // Integer.valueOf(100)
Integer b = 100; // Integer.valueOf(100) 
Integer c = 200; // Integer.valueOf(200)
Integer d = 200; // Integer.valueOf(200)

System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(c.equals(d)); //true 

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); // 超出缓存范围时创建新对象
}

缓存池机制Java对-128 ~ 127范围内的Integer对象预先生成并缓存ab指向同一个缓存对象,a == b比较对象地址,返回true200超出默认缓存范围(-128 ~ 127),Integer.valueOf(200)每次会创建新对象,cd指向不同对象,c == d比较对象地址,返回false

对于 Integer,可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size> 修改缓存上限,但不能修改下限 -128。实际使用时,并不建议设置过大的值,避免浪费内存,甚至是 OOM(全称Out Of Memory, 即内存溢出)

  • 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
  • 内存泄漏:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。

对于Byte,Short,Long ,Character 没有类似 -XX:AutoBoxCacheMax 参数可以修改,因此缓存范围是固定的,无法通过 JVM 参数调整。Boolean 则直接返回预定义的 TRUEFALSE 实例,没有缓存范围的概念。

Character的缓存逻辑

public static Character valueOf(char c) {
    if (c <= 127) { // must cache
      return CharacterCache.cache[(int)c];
    }
    return new Character(c);
}

Boolean的缓存逻辑

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

两种浮点数类型的包装类 Float,Double 并没有实现缓存机制

Float a = 3f;
Float b = 3f;
System.out.println(a == b);// 输出 false

Double c = 1.2;
Double d = 1.2;
System.out.println(c == d);// 输出 false

下面这段代码的输出结果是什么?

Integer a = 40;
Integer b = new Integer(40);
System.out.println(a == b);

Integer a = 40自动装箱等价于Integer a = Integer.valueOf(40)40在默认缓存范围内,所以a直接使用的是缓存中的对象,而Integer b = new Integer(40)会直接创建新的对象。因此,答案是false


网站公告

今日签到

点亮在社区的每一天
去签到