Integer缓存池之谜:-128到127之外,为何==判等必为false?
一、现象直击:一个让90%开发者踩坑的经典陷阱
java
1Integer a = 127, b = 127;
2System.out.println(a == b); // true ✅
3
4Integer c = 128, d = 128;
5System.out.println(c == d); // false ❌
6
同样的赋值方式,同样的数值,只因跨过了127这条隐形分水岭,==的结果就从true翻转为false。这不是bug,而是Java精心设计的缓存优化机制在背后作祟。
二、追根溯源:IntegerCache——藏在Integer里的"对象池"
翻开JDK源码,Integer类内部藏着一个静态内部类IntegerCache,这才是一切的根源:
java
1private static class IntegerCache {
2 static final int low = -128; // 硬编码,不可改
3 static final int high; // 默认127,可调
4 static final Integer cache[];
5
6 static {
7 int h = 127;
8 String prop = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
9 if (prop != null) {
10 try {
11 int i = Integer.parseInt(prop);
12 i = Math.max(i, 127); // 低于127自动回退
13 h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
14 } catch (NumberFormatException ignored) {}
15 }
16 high = h;
17 cache = new Integer[(high - low) + 1];
18 int j = low;
19 for (int k = 0; k < cache.length; k++)
20 cache[k] = new Integer(j++); // 类加载时一次性预创建所有缓存对象
21 }
22 private IntegerCache() {}
23}
24
三个铁律请刻进脑子里:
- 下限-128是JLS(Java语言规范)强制要求的,源于byte类型的最小值,所有JVM必须遵守,永不可改
- 上限默认127,但可通过JVM参数调高:
-Djava.lang.Integer.IntegerCache.high=256或-XX:AutoBoxCacheMax=256 - 缓存数组在类加载时就初始化完毕,256个对象长期存活,不受GC影响
三、==为何在128之外"失灵"?——valueOf()才是真正的幕后推手
关键在于:当你写Integer c = 128;时,编译器并不是直接new对象,而是自动装箱,等价于:
java
1Integer c = Integer.valueOf(128);
2
而valueOf()的实现逻辑极其冷酷:
java
1public static Integer valueOf(int i) {
2 if (i >= IntegerCache.low && i <= IntegerCache.high)
3 return IntegerCache.cache[i + (-IntegerCache.low)]; // 命中缓存,返回同一对象
4 return new Integer(i); // 未命中,新建对象
5}
6
127的命运:在缓存范围内,valueOf(127)直接从cache数组取出预创建好的对象,c和d指向同一个地址,==返回true。
128的命运:超出缓存范围,valueOf(128)每次都new Integer(128),c指向地址A,d指向地址B,==比较的是地址而非数值,自然返回false。
用一张图看清本质:
1Integer缓存池 [-128 ─────── 127] ← 256个预创建对象,共享同一引用
2 ↑ ↑
3 127在池内 128在池外
4 a、b同地址 c、d各new一个
5 a==b → true c==d → false
6
四、三大"陷阱场景",一个比一个隐蔽
| 场景 | 代码 | ==结果 | 原因 |
|---|---|---|---|
| 自动装箱(范围内) | Integer a=100; Integer b=100; | true | valueOf命中缓存 |
| 自动装箱(范围外) | Integer c=200; Integer d=200; | false | valueOf未命中,各new一个 |
| 显式new(范围内) | Integer e=new Integer(127); Integer f=new Integer(127); | false | new永远绕过缓存,强制新建 |
第三个场景最致命——即便数值在缓存范围内,只要用了new,缓存机制就形同虚设。这也是为什么阿里Java开发规范明确禁止使用new Integer()。
五、为何偏偏是-128到127?——不是拍脑袋,是精心计算
这个范围的选择背后有三重考量:
- 规范强制:JLS §5.1.7明确要求必须缓存-128到127,这是所有JVM的最低保障
- 覆盖99%高频场景:循环索引、HTTP状态码(200、404)、枚举序号、数组长度、小计数器……日常开发中绝大多数"小整数"都落在此区间
- 内存代价极小:256个Integer对象,每个约16字节,总共不过几KB。若扩大到1000,缓存翻4倍,收益却急剧递减
下限-128对应byte的最小值,是语言设计的历史惯性;上限127则是性能与内存的最优平衡点。
六、正确姿势:三招彻底告别==陷阱
第一招(首选):永远用equals()
java
1Integer c = 128, d = 128;
2System.out.println(c.equals(d)); // true ✅
3// 防空指针写法:
4if (c != null && c.equals(d)) { ... }
5
第二招:强制拆箱比基本类型
java
1System.out.println(c.intValue() == d.intValue()); // true ✅
2// 简写(Java自动拆箱):
3System.out.println(c == d.intValue()); // true ✅
4
第三招(不推荐):调大缓存上限
bash
1java -Djava.lang.Integer.IntegerCache.high=500 YourMain
2# 或
3java -XX:AutoBoxCacheMax=500 YourMain
4
但请注意:设为小于127会被JVM自动修正为127;设得再大,new Integer(200)照样不走缓存。这招治标不治本,不同环境配置不一致反而引入新的不可预测性。
七、举一反三:其他包装类的缓存地图
| 包装类 | 缓存范围 | 备注 |
|---|---|---|
| Byte | -128 ~ 127 | 全部缓存(byte总共就256个值) |
| Short | -128 ~ 127 | 固定范围 |
| Long | -128 ~ 127 | 固定范围 |
| Character | 0 ~ 127 | ASCII码范围 |
| Boolean | TRUE / FALSE | 仅两个值,全部缓存 |
| Float | ❌ 无缓存 | 浮点数值域无限,无法预缓存 |
| Double | ❌ 无缓存 | 同上 |
所以Character c1='A'; Character c2='A';中,'A'的ASCII码65在缓存内,c1==c2为true——同理可证。
八、终极总结:三句话刻进DNA
① Integer的128陷阱不是bug,是JLS强制的缓存优化,所有JVM必须遵守。
② ==比的是地址,equals()比的是值——包装类判等,永远用equals(),没有例外。
③ 自动装箱调valueOf(),new Integer()绕过缓存——写代码时多一个关键字,结果天差地别。
下次再写Integer a = 128; Integer b = 128;时,请默念:你以为的相等,不过是两个陌生人碰巧穿了同一件衣服。 用equals(),才能看穿它们的灵魂。