java是值传递,传递的是对象引用的副本,因此修改对象状态可见,但重新赋值不可见;string不可变、stringbuilder可变,integer存在缓存与装箱陷阱。

Java里Object参数修改后原对象为啥变了?
因为传的是对象引用的副本,不是对象本身。你拿到的obj是原引用值的一个拷贝,它和原引用指向同一个堆内存地址。所以通过这个副本调用setXxx(),自然能改到原对象的状态。
常见错误现象:误以为“对象被传进去了”,就等于“对象本身被传递了”;结果在方法里把obj = new SomeClass(),发现外面没变——那是因为你只是把副本指向了新地址,原引用还稳稳指着老地方。
- 所有
Object类型(包括自定义类、String、ArrayList等)都遵循这一规则 -
String看似不变,是因为它不可变(concat()或+会生成新对象),不是传递机制特殊 - 如果想让方法内重新赋值对外生效,只能靠返回新对象,或用包装容器(如
AtomicReference、数组长度为1的Object[])
int和Integer传参行为为啥看起来不一致?
根本区别不在“基本类型 vs 包装类”,而在是否发生自动装箱和对象复用。传int:实实在在复制一个4字节整数;传Integer:复制的是引用值(指向常量池或堆中某个Integer实例)。
使用场景:比如Integer a = 127; Integer b = 127;,a == b为true(缓存命中);但Integer c = 128; Integer d = 128;,c == d为false(堆上新建)。一旦你在方法里写i = i + 1,就触发自动拆箱+装箱,大概率生成新对象。
- 永远别用
==比较两个Integer的值,用.equals() - 方法参数是
Integer时,i++或i = 100不会影响外部变量所持的引用 - 性能影响:高频装箱/拆箱会增加GC压力,尤其在循环中
为什么StringBuilder传进去能append,String却不能“改”?
StringBuilder是可变对象,append()操作直接修改其内部字符数组;String是不可变类,任何“修改”操作(如substring()、replace())都返回新实例,原引用不变。
容易踩的坑:写了个方法void mutate(String s) { s = s + "x"; },调用后发现原String没变——不是传递失效,是你只改了副本引用的目标,而String自己又不提供就地修改接口。
- 如果真需要“传出字符串”,要么返回
String,要么用StringBuilder作为参数(并确保调用方传入的是同一个实例) - 注意
StringBuilder线程不安全,多线程共享同一实例时要加锁 - 从JDK 9起,
String内部实现从char[]换成byte[],但不影响传递语义
怎么验证一个参数到底是值传递还是引用传递?
最可靠的方式是看「方法内重新赋值」是否影响外部变量。写个简单测试:传一个ArrayList,在方法里先list.add("a")(状态改变可见),再list = new ArrayList()(之后的操作对外不可见)。
关键判断点:只要方法内对形参做=赋值,且该赋值目标是新对象或基本值,外部一定看不到变化。这是值传递铁证——你复制的是“引用的值”,不是“引用本身”。
- 不要依赖IDE调试时看到的变量名相同就认为是同一个引用
- JVM规范明确写出:Java中只有值传递(
pass-by-value),没有引用传递(pass-by-reference) - 真正容易混淆的,是“引用类型的值”这个概念——它本身是个内存地址数字,也是可以被复制的
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











