在Java中,我们有时会使用包装类 Integer。观察下面这段简单的代码,你能准确预测它的输出吗?
public static void main(String[] args) {
Integer a = 110;
Integer b = 110;
Integer c = 130;
Integer d = 130;
System.out.println(a == b); // 输出什么?
System.out.println(c == d); // 输出什么?
}
运行结果出乎许多人的意料:
true
false
为什么? 明明都是 Integer 对象的比较,a == b 为 true(相等),而 c == d 却为 false(不相等)?这背后的秘密就是Java的 “128陷阱”(或更准确地说是 整数缓存机制)。
根源剖析:自动装箱与 Integer.valueOf()
关键在于 Integer a = 110; 这行代码。这并非简单地创建一个新的 Integer 对象。在编译时,Java会自动将其转换为:
Integer a = Integer.valueOf(110);
Integer b = Integer.valueOf(110);
Integer c = Integer.valueOf(130);
Integer d = Integer.valueOf(130);
核心秘密就藏在 Integer.valueOf(int i) 方法中! 让我们看看它的源码:
这个方法的行为决定了对象是复用还是新建:
-
检查范围: 首先判断传入的整数值
i是否在某个特定范围内(IntegerCache.low到IntegerCache.high)。 -
命中缓存: 如果
i在范围内,方法直接从IntegerCache.cache数组中返回一个预先创建好的Integer对象。 -
新建对象: 如果
i不在范围内,则new一个全新的Integer对象返回。
幕后功臣(或“陷阱”制造者):IntegerCache
IntegerCache 是 Integer 类内部的一个私有静态类,专门负责缓存一定范围内的整数对象。其关键部分如下:
private static class IntegerCache {
static final int low = -128; // 下限固定为-128
static final int high; // 上限可配置
static final Integer cache[]; // 缓存数组
static {
int h = 127; // 默认上限是127
// 尝试从系统属性读取配置的上限值
String highProp = ...;
if (highProp != null) {
try {
h = Math.max(Integer.parseInt(highProp), 127); // 确保至少127
h = Math.min(h, Integer.MAX_VALUE - (-low) -1); // 防止数组过大
} catch (Exception ignore) { }
}
high = h;
// 创建缓存数组并初始化值(从low到high)
cache = new Integer[(high - low) + 1];
int j = low;
for (int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++); // 预先创建好对象
}
}
// ... 其他省略
}
核心要点:
- 默认范围: -128 到 127(low = -128, high = 127)。
- 可配置上限: 可以通过JVM参数 (-Djava.lang.Integer.IntegerCache.high=) 将上限调整到大于127(但不能小于127,下限固定-128)。
- 预加载: 在类加载时,静态初始化块就会创建好这个范围内(例如默认的-128到127)的所有 Integer 对象,并存储在 cache 数组中。
谜底揭晓:true 和 false 的原因
现在,让我们重新审视最初的代码:
-
Integer a = 110;和Integer b = 110;-
110 在默认缓存范围 (-128 到 127) 之内。
-
valueOf(110)两次调用都返回IntegerCache.cache数组中同一个预先创建好的Integer对象。 -
a == b比较的是对象的内存地址,由于是同一个对象,结果为true。
-
-
Integer c = 130;和Integer d = 130;-
130 超出了默认缓存范围 (-128 到 127)。
-
valueOf(130)两次调用都触发了new Integer(130),各自创建了一个全新的Integer对象。 -
c == d比较的是两个不同对象的内存地址,结果自然是false。
-
这就是“128陷阱”的本质: 对于 -128 到 127(默认)之间的小整数,自动装箱 (valueOf) 会复用缓存中的同一个对象,导致 == 比较地址时为 true;对于此范围外的整数,每次装箱都可能(且通常)创建一个新对象,导致 == 比较地址时为 false。
如何正确比较 Integer 的值?
“==” 运算符比较的是对象的引用(内存地址),而不是它们包装的整数值! 要比较两个 Integer 对象代表的数值是否相等,必须使用 equals() 方法:
System.out.println(a.equals(b)); // true (110 == 110)
System.out.println(c.equals(d)); // true (130 == 130)
或者,利用自动拆箱(将 Integer 自动转换为 int)进行数值比较:
System.out.println(a.intValue() == b.intValue()); // true
System.out.println(c.intValue() == d.intValue()); // true
// 或者更简洁地(依赖自动拆箱):
System.out.println(a == b.intValue()); // true (a拆箱成int与b的值比较)
System.out.println(c == d); // 注意!这里c自动拆箱成int,d也自动拆箱成int,比较数值 -> true
// 但强烈建议明确意图:使用 equals 或 显式拆箱/基本类型比较
总结
Java的 Integer 类通过 IntegerCache 对常用的小整数(默认为 -128 到 127)进行了缓存优化,以减少对象创建开销。Integer.valueOf() 方法利用了这个缓存。
-
当通过自动装箱(如
Integer i = 100;)或直接调用Integer.valueOf()获取一个在缓存范围内的整数对象时,返回的是缓存中同一个对象的引用。 -
当获取一个超出缓存范围的整数对象时,
valueOf()会创建一个新的Integer对象。
因此,使用 == 比较两个 Integer 对象的引用时,结果取决于它们的值是否在缓存范围内。 这常常导致不符合直觉的结果(“陷阱”)。
最佳实践: 总是使用 equals() 方法来比较两个 Integer 对象(或其他包装类对象)所包装的值是否相等。 避免依赖 == 进行包装类对象的数值比较,除非你非常清楚它们引用的是缓存中的同一个对象。理解“128陷阱”的机制对于编写健壮且符合预期的Java代码至关重要。

24

被折叠的 条评论
为什么被折叠?



