# 2. 基本类型和包装类型区别、存储方式?

# 标准答案

Java 提供 基本数据类型(primitive types)和 包装类型(wrapper classes)。基本类型直接存储在栈上,存储的是值本身,而包装类型是对象,存储在堆上,存储的是对象引用。包装类型提供了更多的方法和功能,但在性能上比基本类型略低,且存在自动装箱(Autoboxing)和拆箱(Unboxing)带来的额外开销。

# 答案解析

Java 的数据类型可以分为:

  1. 基本数据类型(Primitive Types)

    • 存储的是值本身,位于局部变量表,不会产生额外的对象开销。
    • 不支持 null,没有方法调用,只能通过运算符操作。
    • 具体类型:
      • 整数类型byte(1B)、short(2B)、int(4B)、long(8B)
      • 浮点类型float(4B)、double(8B)
      • 字符类型char(2B,使用 UTF-16 存储)
      • 布尔类型boolean(JVM 规范未定义具体大小,通常 1B)
  2. 包装类型(Wrapper Classes)

    • 属于 引用类型,对象存储在 中,通过引用访问。
    • 可以使用 null,提供方法,如 Integer.valueOf()Double.parseDouble() 等。
    • 具体类型:
      • ByteShortIntegerLong
      • FloatDouble
      • Character
      • Boolean

# 存储方式对比

数据类型 存储位置 变量存储内容 是否支持 null 是否有额外开销
基本类型 栈/局部变量表 值本身
包装类型 堆(对象)+ 栈(引用) 对象引用

# 自动装箱与拆箱(Autoboxing & Unboxing)

Java 提供了 自动装箱(基本类型 → 包装类型)和 自动拆箱(包装类型 → 基本类型)的机制,使基本类型和包装类型可以无缝转换。例如:

Integer num = 10; // 自动装箱,相当于 Integer.valueOf(10)
int val = num;    // 自动拆箱,相当于 num.intValue()
1
2

性能问题:频繁的装箱和拆箱会导致额外的对象创建,影响性能。

# 包装类型的缓存机制

为了优化性能,Java 对部分包装类型进行了缓存,减少对象创建:

  • Boolean.TRUE / Boolean.FALSE 缓存 truefalse
  • ByteShortIntegerLong-128 ~ 127 之间的值会被缓存
  • Character 缓存 0 ~ 127 之间的值
  • FloatDouble 没有缓存,每次都会创建新对象

示例:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true,指向缓存对象

Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false,超出缓存范围,创建新对象
1
2
3
4
5
6
7

# 常见错误

  1. 使用 == 比较包装类型(导致错误判断)

    Integer a = 128;
    Integer b = 128;
    System.out.println(a == b); // false
    
    1
    2
    3

    原因Integer 超出 -128~127 的缓存范围,创建了不同对象。
    正确做法:使用 equals() 进行比较:

    System.out.println(a.equals(b)); // true
    
    1
  2. 频繁装箱/拆箱导致性能下降

    long sum = 0;
    for (Long i = 0L; i < 1000000L; i++) { // i 是 Long,发生频繁拆箱
        sum += i;
    }
    
    1
    2
    3
    4

    优化方案:使用基本类型:

    long sum = 0;
    for (long i = 0; i < 1000000L; i++) {
        sum += i;
    }
    
    1
    2
    3
    4

# 最佳实践

  1. 能用基本类型就不要用包装类型(避免不必要的装箱和拆箱)
  2. 避免使用 == 比较包装类型,改用 equals()
  3. 合理利用包装类型缓存,避免创建不必要的对象
  4. 避免 List<Integer> 频繁装箱/拆箱,可用 IntStream 替代

# 深入追问

  1. 为什么 int 不能直接赋值为 null?如何设计可空的数值类型?
  2. Java 21 引入的 Value Classes 能否取代包装类型?
  3. AtomicIntegerInteger 的区别,为什么 AtomicInteger 不能用 int 代替?
  4. 为什么 Double 没有缓存机制?如果想优化 Double,可以如何设计?
  5. JIT 编译器是否会优化装箱/拆箱问题?如何分析 JIT 优化行为?

# 相关面试题

  • Integer a = 127; Integer b = 127; System.out.println(a == b); 输出什么?为什么?
  • Integer c = 200; Integer d = 200; System.out.println(c == d); 输出什么?如何优化?
  • 为什么 BooleanByteCharacter 的部分值会缓存,而 FloatDouble 不会?
  • List<Integer> 是否会导致大量装箱操作?如何优化?
  • Optional<Integer>Integer 有什么区别?什么时候应该用 Optional