JVM如何实现原始类型包装类作函数参数时的不可变性
首先明确两个核心前提:
- Java 所有参数传递均为值传递:对于引用类型,传递的是变量存储的对象引用的副本,而非引用本身,更不是堆上的对象实体。
Integer是不可变类:其内部存储实际数值的字段为private final int value,类本身没有提供任何修改该字段的公开方法,正常代码路径下无法修改一个已创建Integer实例的内部值。
编译器对Integer类型++i的语法糖处理
不管++i写在哪个方法里,只要操作的是Integer类型变量,编译器都会自动插入拆箱、计算、装箱的逻辑,等价代码如下:
// 源码写的 ++i; 编译后实际执行逻辑 i = Integer.valueOf(i.intValue() + 1);
这个操作从来没有修改原Integer对象的内部值,而是生成了一个新的(或从Integer缓存池取出对应值的)Integer实例,再把当前作用域下的变量i重新指向这个新实例。
两个场景的行为差异解释
main方法内的++i生效原因
main方法栈帧中的局部变量i初始指向你创建的、存储值为3的Integer实例:
- 执行
++i时先拆箱取出值3,计算得到4 - 通过
Integer.valueOf(4)获取存储值为4的Integer实例 - 将main栈帧内的局部变量
i重新指向这个值为4的实例
原存储3的Integer实例没有任何修改,只是变量不再指向它,因此后续打印得到4。
方法内++i不影响外部变量的原因
调用changeInteger(i)时,JVM会将main方法中i存储的引用(指向值为4的Integer实例的地址)拷贝一份,传入changeInteger方法的栈帧,赋值给形参i。此时两个作用域的i是完全独立的变量,只是初始指向堆上同一个Integer实例。
在changeInteger内执行++i时:
- 同样拆箱计算得到5,获取值为5的Integer实例
- 仅将changeInteger栈帧内的形参
i重新指向这个值为5的新实例
整个过程不会触碰main方法栈帧内的局部变量i,方法执行结束出栈后,形参i直接被回收,main中的i始终指向原来值为4的实例,因此打印结果仍为4。
常见误区澄清
不少人会混淆“修改可变对象的内部状态”和“给变量重新赋值”的区别:如果传入方法的是可变对象(比如
ArrayList、自定义的有setter的普通对象),方法内通过引用调用对象的修改方法(如add()、setXxx()),是直接修改堆上的对象本身,外部变量指向同一个对象,自然能看到变化。但对于不可变类,所有看似“修改”的操作本质都是生成新对象+当前作用域变量重定向,完全无法跨作用域影响其他变量。
内容的提问来源于stack exchange,提问作者Troskyvs

