Java 是值传递还是引用传递?
结论:Java 只有值传递(pass-by-value)。方法拿到的是实参的副本:基本类型复制数值本身;引用类型复制的是"引用的值"(可以理解为对象地址)。所以方法内可以修改对象的内容,但让形参重新指向另一个对象,调用方不受影响。常说的"Java 是引用传递"是对现象的误称——传的不是引用变量本身,而是引用的副本。
1. 内存里发生了什么
调用前: 进入方法后:
main 栈帧 method 栈帧
n = 10 n' = 10 ← 数值副本
u ──→ [User{name}] u' ──→ [User{name}] ← 同一个堆对象
arr ──→ [int[]{1}] arr' ──→ [int[]{1}] ← 同一个数组
u' = new User(...) 只让副本指向新对象,main 里的 u 仍指向原对象
判断口诀:看方法有没有重新赋值形参。改字段/元素 → 影响调用方;重新赋值引用 → 不影响。
2. 代码:三种字段的实测
class User {
String name;
User(String name) { this.name = name; }
}
public class PassByValueDemo {
static void modify(int n, User u, int[] arr) {
n = 99; // 副本,无效
u.name = "changed"; // 改同一对象的字段,有效
u = new User("new"); // 只改副本引用,无效
arr[0] = 99; // 改同一数组元素,有效
}
/** 经典失败案例:交换两个引用 */
static void swap(User a, User b) {
User tmp = a;
a = b;
b = tmp; // 交换的是副本,调用方毫无感知
}
public static void main(String[] args) {
int n = 1;
User u = new User("A");
int[] arr = {1};
modify(n, u, arr);
System.out.println(n); // 1
System.out.println(u.name); // changed
System.out.println(arr[0]); // 99
User x = new User("X"), y = new User("Y");
swap(x, y);
System.out.println(x.name + y.name); // XY,没有交换
}
}
3. 现象与本质对照
| 操作 | 是否影响调用方 | 本质 |
|---|---|---|
| 修改基本类型形参 | 否 | 数值副本 |
| 修改对象的字段 | 是 | 引用副本指向同一对象 |
形参重新 new 一个对象 | 否 | 只改了副本引用 |
| 修改数组元素 / 集合内容 | 是 | 引用副本指向同一容器 |
| 交换两个引用 | 否 | 交换的是副本 |
传入 null 后在方法内赋值 | 否 | 副本有了新地址,调用方仍是 null |
传入 String 后"修改" | 否 | String 不可变,方法内只会产生新对象 |
4. 为什么 String 看起来像"值传递"
static void change(String s) {
s = s + "!";
}
String s = "hi";
change(s);
System.out.println(s); // hi,不是 hi!
String 是不可变(immutable)的,s + "!" 创建了新对象并让形参指向它,原对象没变。同理,Integer、LocalDate 等不可变类型都表现一致。
5. 常见追问
| 追问 | 回答要点 |
|---|---|
| 那为什么说"传引用"? | 传的是引用的副本,共享对象但独立变量,属于值传递的一种 |
C++ 的 & 引用和 Java 一样吗? | 不一样,C++ 引用是别名,能真正交换;Java 只能改对象内容 |
| 想让方法"交换"两个变量怎么办? | 返回封装对象/数组,或用 AtomicReference、Map.Entry 等容器 |
| 为什么方法内改集合会生效? | 集合是对象,形参与实参指向同一个堆对象 |
| 基本类型包装类传参有坑吗? | 有,Integer 不可变,方法内 i++ 只改副本,调用方不变 |
| 传大对象要注意什么? | 值是引用的副本,开销小;但方法内修改会影响调用方,注意副作用与并发 |
小结
Java 只有值传递:基本类型传值、引用类型传"引用的值"。记住判断标准——改内容会同步,换引用不同步。理解这一点,才能解释为什么 swap 无效、为什么 String 参数"改不动"、为什么集合参数会被方法改掉。