Java的包装类
为什么需要包装类
Java 作为典型的面向对象(Object-Oriented Programming, OOP)编程语言,其语法体系中包含 8 种基本数据类型(byte、short、int、long、float、double、char、boolean)。这些基本数据类型属于值类型,并非对象实例,它们无自身的属性和方法,也不具备继承、多态等面向对象核心特性,因此无法直接参与面向对象范式下的操作。
Java 标准库中的大量核心组件均基于对象设计,仅支持处理对象实例,无法直接存储或操作基本数据类型。为填补这一适配性缺口,Java 提供了包装类:包装类将每种基本数据类型封装为对应的类对象,使基本数据类型能够以引用类型的形式参与面向对象的操作体系。
基本数据类型与包装类的对应关系
所有包装类均位于 java.lang 包下,无需手动导入,对应关系如下:
每个基本数据类型都有其对应的引用数据类型包装类:
| 基本数据类型 | 包装类 | 特殊说明 |
|---|---|---|
| byte | Byte | 全缓存(-128~127) |
| short | Short | 缓存 - 128~127 |
| int | Integer | 缓存 - 128~127(可配置上限) |
| long | Long | 缓存 - 128~127 |
| float | Float | 无缓存 |
| double | Double | 无缓存 |
| char | Character | 缓存 0~127(ASCII 字符) |
| boolean | Boolean | 缓存 TRUE/FALSE 两个常量 |
除 Integer/Character 外,其余包装类名称均为基本类型首字母大写
装箱与拆箱
自动拆箱: Java 编译器自动将包装类对象转换为其对应的基本数据类型的过程
java
Integer numObj = 10;
int num = numObj;
int num = numObj; 编译器实际做的是 int num = numObj.intValue();,调用的是 Integer.intValue(); 方法
自动装箱: Java 编译器自动将基本数据类型转换为其对应的包装类对象的过程
java
int num = 10;
Integer numObj = num;
Integer numObj = 10; 编译器实际做的是 Integer numObj = Integer.valueOf(10);,调用的是 Integer.valueOf(); 方法
极大简化了在基本类型和包装类型之间转换的代码,使代码更简洁、易读,它模糊了基本类型和对象类型在代码中的界限。
包装类属于引用类型,其引用变量可以被赋值为 null(无指向任何对象实例);而自动拆箱的底层机制是 JVM 自动调用包装类对应的实例方法(如 Integer 调用 intValue()、Double 调用 doubleValue()、Boolean 调用 booleanValue() 等,统称 xxxValue() 方法)。若包装类引用变量的值为 null,意味着该引用未指向任何包装类对象实例,此时调用 xxxValue() 这一实例方法会直接触发 NullPointerException。
不可变性
Java 中所有包装类均属于不可变类(Immutable Class),即包装类对象实例一旦创建完成,其内部承载的核心数值便无法被修改。
包装类的不可变性通过以下核心设计机制严格保障:
首先是包装类用于存储数值的核心成员变量均被 private final 双重关键字修饰
如 java.lang.Integer 的源码定义:
java
public final class Integer extends Number implements Comparable<Integer> {
// 核心数值成员
private final int value;
// 构造方法
public Integer(int value) {
this.value = value;
}
}
private 修饰:核心数值成员仅允许在包装类内部访问,外部代码无法直接读取或修改该数值;
final 修饰:对于基本类型的 final 成员变量,其值在通过构造方法完成初始化后便永久固定,不存在被重新赋值的可能;
无公共修改接口:包装类未提供任何如 setValue() 之类的公共方法,不存在通过方法修改核心数值的路径。
其次是类的不可继承性
所有包装类均被 final 关键字修饰(如 final class Integer),使其无法被继承,这一设计避免了子类通过重写方法篡改核心数值的逻辑,进一步加固了包装类的不可变性。
第三是引用重定向而非对象修改
当对指向包装类对象的引用变量执行赋值操作时,并非改变原有包装类对象的核心数值,而是完成以下过程:
创建一个承载新数值的包装类对象实例,将原引用变量的指向从原有对象切换至该新对象,原有包装类对象若不再被任何引用变量指向,会被 Java 垃圾回收机制标记为可回收,最终被清理。
128陷阱
"128 陷阱" 是 Java 中 Integer 包装类因缓存机制导致的典型现象:
当通过自动装箱(底层调用 Integer.valueOf())创建值为 -128~127 的 Integer 对象时,JVM 会复用缓存池中的对象;而创建值为 128(及超出 -128~127 范围)的 Integer 对象时,会新建对象实例。此时用 ==(比较引用地址)比较值为 128 的两个 Integer 对象,会出现数值相等但引用不等,返回 false 的结果,这一现象被称为 "128 陷阱"。
Integer 类中的源码如下:

java
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
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);
// Maximum array size is Integer.MAX_VALUE
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() {}
}
从源码看出:
Integer 类内部维护了一个静态的 Integer 对象缓存池,这个缓存池默认缓存了 -128 到 127 之间的所有整数对象。Integer.valueOf(int i) 是创建 Integer 对象的核心静态方法,其执行逻辑完全依赖缓存池,如果 i 的值在 -128 到 127 的范围内,方法会直接返回缓存池中预先创建好的对应的 Integer 对象的引用。如果 i 的值超出了这个范围,方法会 new 一个全新的 Integer 对象并返回这个新对象的引用。
equals()
Integer 内重写了 equals() 方法,源码如下:

由源码我们可以知道,Integer 的 equals() 方法是将其拆箱为基本数据类型,比较数值相等性
我们看这样一份代码:

下面分析输出结果:
System.out.println(a == b);→ true:a 和 b 是基本类型 int,== 比较值的相等性;System.out.println(a1 == b1);→ true:a1 和 b1 通过自动装箱赋值相当于 Integer.valueOf(10),由于 10 在 Integer 缓存范围内(-128~127),所以 a1 和 b1 指向同一个缓存对象,== 比较对象引用地址;System.out.println(a2 == b2);→ false,new Integer(10) 显式创建新对象,无论值是否在缓存范围内,a2 和 b2 指向堆中两个不同对象,== 比较引用地址;System.out.println(a1 == a);→ true:a1 是 Integer 对象,a 是基本类型 int,当包装类与基本类型用 == 比较时,触发自动拆箱 a1.intValue(),实际比较值的相等性;System.out.println(a1.equals(a));→ true:a1.equals(a) 调用 Integer.equals() 方法,触发自动拆箱 a1.intValue(),equals() 比较值的相等性;System.out.println(a1 == a2);→ false:a1 指向缓存对象,a2 指向显式创建的堆对象,== 比较对象引用地址;System.out.println(a == a2);→ true:a 是基本类型,a2 是包装类对象,触发自动拆箱 a2.intValue(),实际比较值相等。