Integer缓存池之谜:-128到127之外,为何==判等必为false?

0 阅读5分钟

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

三个铁律请刻进脑子里

  1. 下限-128是JLS(Java语言规范)强制要求的,源于byte类型的最小值,所有JVM必须遵守,永不可改
  2. 上限默认127,但可通过JVM参数调高-Djava.lang.Integer.IntegerCache.high=256 或 -XX:AutoBoxCacheMax=256
  3. 缓存数组在类加载时就初始化完毕,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         ab同地址          c、d各new一个
5         a==b → true        c==d → false
6

四、三大"陷阱场景",一个比一个隐蔽

场景代码==结果原因
自动装箱(范围内)Integer a=100; Integer b=100;truevalueOf命中缓存
自动装箱(范围外)Integer c=200; Integer d=200;falsevalueOf未命中,各new一个
显式new(范围内)Integer e=new Integer(127); Integer f=new Integer(127);falsenew永远绕过缓存,强制新建

第三个场景最致命——即便数值在缓存范围内,只要用了new,缓存机制就形同虚设。这也是为什么阿里Java开发规范明确禁止使用new Integer()


五、为何偏偏是-128到127?——不是拍脑袋,是精心计算

这个范围的选择背后有三重考量:

  1. 规范强制:JLS §5.1.7明确要求必须缓存-128到127,这是所有JVM的最低保障
  2. 覆盖99%高频场景:循环索引、HTTP状态码(200、404)、枚举序号、数组长度、小计数器……日常开发中绝大多数"小整数"都落在此区间
  3. 内存代价极小: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固定范围
Character0 ~ 127ASCII码范围
BooleanTRUE / 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(),才能看穿它们的灵魂。