自动装箱和拆箱是编译器在编译期插入Integer.valueOf()和intValue()等方法调用,JVM仅执行普通方法调用;Integer缓存范围为-128~127,超出则新建对象;Boolean、Character等缓存规则各异,Float/Double不缓存;隐式拆箱易引发NullPointerException。

自动装箱和拆箱本质是编译器生成的隐式方法调用
Java 的自动装箱(autoboxing)和拆箱(unboxing)不是 JVM 层的魔法,而是由 javac 编译器在编译期插入的代码。比如 Integer i = 100; 实际被转为 Integer i = Integer.valueOf(100);;而 int x = i; 被转为 int x = i.intValue();。JVM 本身只认对象和基本类型,不理解“自动”二字。
这意味着:运行时看不到额外的“装箱指令”,只有方法调用字节码(如 invokestatic Integer.valueOf 和 invokevirtual Integer.intValue)。这也解释了为什么某些情况下会抛出 NullPointerException——拆箱时如果引用为 null,intValue() 就直接炸了。
Integer.valueOf() 有缓存,但只对 -128 到 127 有效
这是最容易踩坑的地方。当你写 Integer a = 100; Integer b = 100;,a == b 是 true;但换成 Integer a = 200; Integer b = 200;,a == b 就是 false。原因在于 Integer.valueOf(int) 内部做了缓存优化:
- 对于
-128 ~ 127范围内的值,复用静态缓存数组里的Integer实例 - 超出该范围,每次调用都新建
Integer对象 - 这个范围可通过 JVM 参数
-Djava.lang.Integer.IntegerCache.high=xxx扩大,但启动时就得设好,且只影响valueOf,不影响new Integer()
所以永远别用 == 比较两个包装类型变量,除非你明确知道它们来自缓存且不会为 null。
立即学习“Java免费学习笔记(深入)”;
Boolean、Character 等也有缓存,但规则不同
不是所有包装类都像 Integer 那样提供可配置的数值范围缓存:
-
Boolean:只有两个实例,Boolean.TRUE和Boolean.FALSE,valueOf(boolean)总是返回这两个之一 -
Character:缓存\u0000到\u007f(即 0~127),不可配置 -
Byte、Short、Long:缓存固定范围-128 ~ 127,不可配置 -
Float、Double:valueOf()从不缓存,每次必新建对象
这意味着 Float a = 1.0f; Float b = 1.0f; 一定满足 a != b(引用比较),哪怕值相等。
拆箱空指针异常比想象中更隐蔽
最危险的不是显式写 i.intValue(),而是藏在算术、比较、条件表达式里的隐式拆箱:
-
Integer i = null; int x = i + 1;→ 报NullPointerException -
Integer i = null; if (i == 0) {...}→ 拆箱失败,同样 NPE -
List→ 中途遇到list = Arrays.asList(1, 2, null); int sum = list.stream().mapToInt(x -> x).sum(); null就崩
这类问题在重构时尤其容易漏掉:把 int 参数改成 Integer,看似只是加个问号,实则引入了 null 支配的不确定性。上线后某次传入 null,服务就挂。










