因为integer在-128到127范围内复用缓存对象,超出范围则新建实例;==比较引用,故127相等而128不等;缓存由integercache实现,范围可调但下界固定为-128。

为什么 Integer.valueOf(127) == Integer.valueOf(127) 但 Integer.valueOf(128) != Integer.valueOf(128)
因为 Integer 在 -128 到 127 范围内复用缓存对象,超出范围则每次新建实例。这和 == 比较的是引用而非值直接相关。
核心逻辑在 IntegerCache:JVM 启动时就初始化一个静态数组,缓存了 [-128, 127] 共 256 个 Integer 实例。调用 valueOf(int) 时先查这个数组,命中就返回缓存对象。
-
Integer.valueOf()是推荐的构造方式,它会走缓存;而new Integer()总是新建对象(Java 9+ 已废弃) - 缓存范围可由 JVM 参数
-XX:AutoBoxCacheMax=200扩大(仅对Integer有效),但不会缩小下界(-128 固定) - 其他包装类也有类似机制:
Byte、Short、Character缓存范围固定且不可调;Long和Double不缓存(JDK 里没实现valueOf(long)的缓存分支)
IntegerCache 在哪?怎么验证它真被用了
源码位置在 java.lang.Integer 内部,是个私有静态类,字段叫 IntegerCache.cache。你不能直接 import 或访问它,但能间接验证。
最简单的验证方式是看对象身份:
立即学习“Java免费学习笔记(深入)”;
System.out.println(Integer.valueOf(127) == Integer.valueOf(127)); // true System.out.println(Integer.valueOf(128) == Integer.valueOf(128)); // false System.out.println(Integer.valueOf(127).equals(Integer.valueOf(127))); // true(值比较,永远成立)
- 注意:IDE 可能对字面量自动优化(比如
Integer i = 127;也会触发valueOf),所以别只靠赋值语句判断 - 反编译
javap -c YourClass能看到字节码里实际调的是Integer.valueOf(int),不是构造器 -
IntegerCache初始化发生在类加载阶段,且是懒加载——第一次调用valueOf时才真正填充数组(不过通常就是启动时)
缓存带来的性能与陷阱:自动装箱场景下最容易翻车
缓存本意是减少小整数对象分配开销,但它让引用相等性变得“不透明”。尤其在集合操作、并发计数、条件判断中容易误判。
- Map 键冲突:如果把
Integer当 key,用==判断是否同一 key,-128~127 会意外命中,128+ 就失效 - Mock 测试失败:用
Mockito.eq(128)匹配参数时没问题,但若被测代码内部用了==做校验,mock 行为可能和真实运行不一致 - 序列化/反序列化后对象必然不等:哪怕原值是 127,反序列化得到的是新对象,
==一定为 false - GC 影响极小:缓存对象生命周期与类加载器绑定,基本不参与 GC,但也不建议人为扩大缓存(比如设成 10000),会白占堆内存
为什么 Boolean 和 Character 也缓存,但规则不同
Boolean 只缓存 TRUE 和 FALSE 两个实例,所以 Boolean.valueOf(true) == Boolean.TRUE 永远成立;Character 缓存 \u0000 到 \u007f(即 0~127),和 Integer 上界一致,但下界是 0 不是 -128。
-
Character.valueOf(char)的缓存逻辑在源码里是硬编码循环填 128 个对象,没有配置项 -
Boolean的缓存甚至没用数组,就两个 static final 字段,连“缓存类”都不需要 - 别试图用
new Boolean(true)绕过缓存——它不进池子,但 JDK 9+ 同样已废弃,且语义上也没必要
缓存行为不是语言规范强制的,而是 OpenJDK 的实现细节。理论上别的 JVM 可以不缓存,但所有主流实现都跟进了——因为太实用。真正要小心的,从来不是“有没有缓存”,而是“有没有意识到它存在”。









