“Java是值传递还是引用传递?”这是经典面试题。本文通过对比Java和C++,从内存模型角度彻底讲清楚这个问题。
结论先行
| 语言 |
传递方式 |
| Java |
只有值传递 |
| C++ |
值传递 + 引用传递(两种都支持) |
什么是值传递和引用传递
值传递(Pass by Value)
将实参的值的副本传给形参。函数内修改形参,不影响实参。
引用传递(Pass by Reference)
将实参的引用(别名) 传给形参。函数内修改形参,实参也被修改。
C++:两种传递方式都有
C++值传递
1 2 3 4 5 6 7 8 9 10
| void changeValue(int x) { x = 100; }
int main() { int a = 10; changeValue(a); cout << a << endl; return 0; }
|
内存示意:
1 2 3 4 5 6 7 8 9 10
| 调用前: main栈帧: [a = 10]
调用时: main栈帧: [a = 10] change栈帧: [x = 10] ← 复制了a的值
修改后: main栈帧: [a = 10] ← 不变 change栈帧: [x = 100]
|
C++引用传递
1 2 3 4 5 6 7 8 9 10
| void changeValue(int& x) { x = 100; }
int main() { int a = 10; changeValue(a); cout << a << endl; return 0; }
|
内存示意:
1 2 3 4 5 6 7
| 调用时: main栈帧: [a = 10] ↑ change栈帧: [x 是 a 的别名]
修改后: main栈帧: [a = 100] ← 通过别名被修改
|
C++指针传递(本质是值传递)
1 2 3 4 5 6 7 8 9 10 11 12 13
| void changePointer(int* p) { *p = 100; p = nullptr; }
int main() { int a = 10; int* ptr = &a; changePointer(ptr); cout << a << endl; cout << ptr << endl; return 0; }
|
指针传递是值传递——传递的是指针的副本。通过指针可以修改指向的数据,但不能修改指针本身。
Java:只有值传递
基本类型的值传递
1 2 3 4 5 6 7 8 9
| void changeValue(int x) { x = 100; }
public static void main(String[] args) { int a = 10; changeValue(a); System.out.println(a); }
|
这个很好理解,和C++值传递一样。
对象类型——传递的是引用的值!
1 2 3 4 5 6 7 8 9 10
| void changeObject(Person p) { p.name = "Jerry"; p = new Person("Nobody"); }
public static void main(String[] args) { Person person = new Person("Tom"); changeObject(person); System.out.println(person.name); }
|
关键理解:Java传递的是引用的值(即对象地址的副本)
内存示意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 调用前: main栈帧: [person = 0x100] ──────▶ 堆: Person{name="Tom"}
调用时(传递引用的副本): main栈帧: [person = 0x100] ──┬──▶ 堆: Person{name="Tom"} change栈帧: [p = 0x100] ───────┘
执行 p.name = "Jerry" 后: main栈帧: [person = 0x100] ──┬──▶ 堆: Person{name="Jerry"} change栈帧: [p = 0x100] ───────┘
执行 p = new Person("Nobody") 后: main栈帧: [person = 0x100] ──────▶ 堆: Person{name="Jerry"} change栈帧: [p = 0x200] ──────────▶ 堆: Person{name="Nobody"} ↑ p指向了新对象,但person没变
|
证明Java是值传递的关键测试
1 2 3 4 5 6 7 8 9 10 11 12 13
| void swap(Person a, Person b) { Person temp = a; a = b; b = temp; }
public static void main(String[] args) { Person p1 = new Person("Tom"); Person p2 = new Person("Jerry"); swap(p1, p2); System.out.println(p1.name); System.out.println(p2.name); }
|
如果Java是引用传递,p1和p2应该被交换。但实际没有,因为交换的只是引用的副本。
对比:C++实现真正的swap
1 2 3 4 5 6 7 8 9 10 11 12 13
| void swap(int& a, int& b) { int temp = a; a = b; b = temp; }
int main() { int x = 1, y = 2; swap(x, y); cout << x << " " << y << endl; return 0; }
|
为什么会混淆
很多人认为”Java对象是引用传递”,是因为混淆了两个概念:
| 概念 |
含义 |
| 引用类型 |
Java中对象变量存储的是引用(地址) |
| 引用传递 |
传参时传递变量本身的别名 |
Java的对象变量确实是引用类型,但传参时传递的是引用的值(副本),所以仍然是值传递。
总结表格
| 场景 |
Java |
C++ |
| 基本类型传参 |
值传递 |
值传递 |
| 对象传参 |
传递引用的副本(值传递) |
默认值传递,可用&引用传递 |
| 能否在函数内让外部变量指向新对象 |
不能 |
引用传递可以 |
| swap函数能否直接交换 |
不能 |
引用传递可以 |
一句话总结
- C++:默认值传递,加
&变成引用传递
- Java:永远是值传递,只不过对象变量的”值”是引用(地址)
记住这个区分:
- 传递方式说的是:传参时是复制值还是传递别名
- 引用类型说的是:变量存储的是地址而非实际数据