引言:为什么Java需要包装类?
Java作为一门"万物皆对象"的面向对象编程语言,在设计上面临着一个重要挑战:如何处理基本数据类型与对象体系的关系?这就引出了我们今天要深入探讨的包装类概念。
基本数据类型与包装类的对应关系

包装类存在的意义:
- 让基本数据类型具备对象的特性
- 可以在集合框架中使用(集合只能存储对象)
- 提供丰富的工具方法(如类型转换、数值处理等)
- 实现null值的表示
深入剖析Integer的128陷阱
现象展示
java
Integer i = 127;
Integer j = 127;
System.out.println(i == j); // 输出:True
Integer k = 128;
Integer l = 128;
System.out.println(k == l); // 输出:False
底层原理:Integer缓存机制
Java为了优化性能,对常用的Integer值进行了缓存:
java
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
缓存范围:-128 到 127
IntegerCache内部实现
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);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch(NumberFormatException nfe) {
// 忽略配置异常
}
}
high = h;
cache = new Integer[(high - low) + 1]; // 缓存数组大小:256
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
}
缓存映射表解析
|------|----------|
| 数组索引 | Integer值 |
| 0 | -128 |
| 1 | -127 |
| ... | ... |
| 128 | 0 |
| 129 | 1 |
| ... | ... |
| 138 | 10 |
| ... | ... |
| 255 | 127 |
计算原理:
// 值为10的Integer在缓存数组中的位置
int index = 10 + 128; // 138
Integer cachedValue = IntegerCache.cache[138]; // 获取值为10的缓存对象
自动拆装箱机制详解
什么是自动拆装箱?
**自动装箱:**基本数据类型 → 包装类
**自动拆箱:**包装类 → 基本数据类型
java
// 自动装箱示例
Integer m = 10;
// 等价于:Integer m = Integer.valueOf(10);
// 自动拆箱示例
int a = m;
// 等价于:int a = m.intValue();
输出结果不同
valueof判断值是否在范围(-128~127)里
Integer 是引用数据类型(包装类)
valueOf() 方法返回的是 Integer 类型的引用
对于引用数据类型,传递地址;基本数据类型传递值

第一个:
比较地址,地址相同,返回true
第二个:
new integer 创建新的地址
两个指向的地址不同
128不在valueof范围里->创建新对象
总结
使用Integer定义的数据会进行自动装箱,自动装箱调用valueOf()方法,该方法会判断我们的入参是否在-128~127之间,如果在这个之间则会返回一个已经存在的对象的地址(该对象在Cache数组当中,Cache数组是一个从-128~127的数组),不会重新创建对象,否则会重新创建对象。
综合测试案例
java
public class Demo {
public static void main(String[] args) {
int a = 10;
int b = 10;
Integer a1 = 10; // 自动装箱,使用缓存
Integer b1 = 10; // 自动装箱,使用缓存
Integer a2 = new Integer(10); // 新建对象
Integer b2 = new Integer(10); // 新建对象
System.out.println(a == b); // true - 基本类型比较值
System.out.println(a1 == b1); // true - 同一缓存对象
System.out.println(a2 == b2); // false - 不同对象
System.out.println(a1 == a); // true - 自动拆箱
System.out.println(a1.equals(a)); // true - 自动拆箱比较值
System.out.println(a1 == a2); // false - 缓存对象 vs 新对象
System.out.println(a == a2); // true - 自动拆箱
}
}
== 与 equals 的深度区别
基本概念
java
// == 比较规则:
// - 基本数据类型:比较值是否相同
// - 引用数据类型:比较地址是否相同
// equals 比较规则:
// - Object类中默认使用 == 比较地址
// - String等类重写了equals方法,比较内容
// - 包装类重写了equals方法,比较值
实际应用场景
== 的使用场景:
- 基本数据类型的值比较
- 引用类型的同一性检查(是否同一个对象)
equals 的使用场景: - 对象内容的比较
- 集合操作中的元素查找和比较
- 需要逻辑相等性判断的场景
final关键字的全面解析
final的不同用法

修饰变量:
java
final int MAX_VALUE = 100; // 常量,不可修改
final List<String> list = new ArrayList<>();
// list引用不可变,但list内容可以修改
修饰方法:
java
public final void cannotOverride() {
// 该方法不能被子类重写
}
修饰类:
java
public final class String { // 该类不能被继承
private final char value[]; // 字符数组不可变
}
final的内存语义
- final 固定的是引用地址,不是对象内容
- = null 和 = "" 都不报错,因为都是在赋值地址
- 只能被赋值一次,在初始化时完成
静态代码块的特殊作用
static代码块的特点
java
public class Example {
static {
// 在类加载时执行,早于main方法
System.out.println("静态代码块执行");
}
public static void main(String[] args) {
System.out.println("main方法执行");
}
}
执行特性:
- 类加载时自动执行
- 只执行一次
- 按代码书写顺序执行
- 常用于静态资源初始化
泛型:类型安全的保障
泛型的基本概念
泛型:泛指一切类型,提供编译期类型安全检查
泛型在栈实现中的应用
java
public class Stack<E> {
private Object[] arr;
private int top;
public Stack(int size) {
arr = new Object[size];
top = -1;
}
public void push(E value) {
if(top == arr.length - 1) {
System.out.println("栈满");
return;
}
arr[++top] = value;
}
@SuppressWarnings("unchecked")
public E pop() {
if(top == -1) {
System.out.println("栈空");
return null;
}
return (E) arr[top--];
}
}
// 使用示例
Stack<Integer> intStack = new Stack<>(10);
Stack<String> strStack = new Stack<>(10);
Stack<Double> doubleStack = new Stack<>(10);
总结与最佳实践
包装类使用要点
- 比较操作:始终使用equals()而不是==比较包装类
- 缓存意识:了解-128到127的缓存范围,避免128陷阱
- null安全:包装类可能为null,使用时注意空指针检查
- 性能考量:在性能敏感场景,考虑使用基本数据类型