Integer拆装箱问题
Integer 拆装箱与缓存机制:为什么 2 == 2 而 200 != 200?
在 Java 中,Integer 作为 int 的包装类,其拆装箱(Autoboxing/Unboxing)机制和缓存策略常常导致看似矛盾的结果,比如两个值相等的 Integer 对象用 == 比较时,有时为 true,有时为 false。本文将深入解析这一现象背后的原理。
自动拆装箱的基本概念
自动装箱(Autoboxing):将基本数据类型(如 int)自动转换为对应的包装类(如 Integer)。
自动拆箱(Unboxing):将包装类(如 Integer)自动转换为对应的基本数据类型(如 int)。
示例:
// 自动装箱:int → Integer
Integer a = 2; // 等价于 Integer a = Integer.valueOf(2);
// 自动拆箱:Integer → int
int b = a; // 等价于 int b = a.intValue();
正是自动装箱机制,使得 Integer a = 2 这种语法成立,而其内部依赖 Integer.valueOf(int) 方法实现。
Integer 缓存机制(IntegerCache)
Integer 的缓存机制是导致 2 == 2 而 200 != 200 的核心原因。Java 为了优化性能,对 -128 到 127 之间的整数 进行了缓存,避免频繁创建相同值的 Integer 对象。
1. Integer.valueOf(int) 方法的实现
Integer.valueOf(int) 是自动装箱的核心方法,其源码逻辑如下:
public static Integer valueOf(int i) {
// 若 i 在缓存范围内(-128~127),直接返回缓存中的实例
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 超出范围则创建新的 Integer 实例
return new Integer(i);
}
2. IntegerCache 内部类
IntegerCache 是 Integer 的静态内部类,负责管理缓存的 Integer 实例:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// 缓存上限默认为 127,可通过 JVM 参数调整(-XX:AutoBoxCacheMax=xxx)
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);
// 缓存上限不能超过 Integer.MAX_VALUE - (-low)
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 忽略无效参数
}
}
high = h;
// 初始化缓存数组,存储 -128 到 high 的 Integer 实例
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
关键特性:
- 缓存范围默认是
-128 ~ 127; - 缓存上限
high可通过 JVM 参数-XX:AutoBoxCacheMax=xxx调整(如调整为 500),但下限-128不可修改; - 缓存数组在类加载时初始化,所有在范围内的
Integer实例被提前创建并存储,后续直接复用。
题目解析:为什么 a == b 为 true 而 c == d 为 false?
回到开头的题目:
Integer a = 2; // 自动装箱:调用 Integer.valueOf(2)
Integer b = 2; // 自动装箱:调用 Integer.valueOf(2)
Integer c = 200; // 自动装箱:调用 Integer.valueOf(200)
Integer d = 200; // 自动装箱:调用 Integer.valueOf(200)
System.out.println(a == b); // true
System.out.println(c == d); // false
原因分析:
a和b的比较:2在-128 ~ 127范围内,Integer.valueOf(2)会返回缓存中的同一个Integer实例。因此a和b指向同一个对象,a == b为true。c和d的比较:200超出了默认缓存范围(>127),Integer.valueOf(200)会创建新的Integer实例。因此c和d指向不同的对象,c == d为false。
拓展:其他包装类的缓存机制
Java 中其他基本类型的包装类也有类似的缓存机制,但范围和实现略有不同:
| 包装类 | 缓存范围 | 特点 |
|---|---|---|
Byte |
-128 ~ 127 |
范围固定,不可修改 |
Short |
-128 ~ 127 |
范围固定,不可修改 |
Character |
0 ~ 127 |
范围固定,缓存 ASCII 字符 |
Long |
-128 ~ 127 |
范围固定,不可修改 |
Integer |
-128 ~ 127(可调整上限) |
唯一可通过 JVM 参数修改缓存上限的类 |
Boolean |
true 和 false |
仅缓存两个静态实例 |
注意事项与最佳实践
==与equals()的区别:==比较对象引用(地址),仅当两个Integer指向同一实例时为true;equals()比较值,无论是否在缓存范围内,只要值相等就为true。
示例:
Integer a = 200; Integer b = 200; System.out.println(a == b); // false(引用不同) System.out.println(a.equals(b)); // true(值相同)避免依赖缓存范围:
缓存上限可通过 JVM 参数修改,因此不要在代码中假设Integer的缓存范围是固定的127,比较值时优先使用equals()。拆箱后的比较:
若Integer与int比较,Integer会自动拆箱为int,此时==比较的是值:Integer a = 200; int b = 200; System.out.println(a == b); // true(a 自动拆箱为 int,比较值)