抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

“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; // 输出: 10(不变)
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) {  // 注意: int&
x = 100; // 直接修改原变量
}

int main() {
int a = 10;
changeValue(a);
cout << a << endl; // 输出: 100(被改了!)
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; // 输出: 100
cout << ptr << endl; // ptr不是nullptr,没变
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); // 输出: 10(不变)
}

这个很好理解,和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); // 输出: Jerry
}

关键理解: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); // Tom(没换!)
System.out.println(p2.name); // Jerry(没换!)
}

如果Java是引用传递,p1p2应该被交换。但实际没有,因为交换的只是引用的副本

对比: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; // 输出: 2 1(交换成功)
return 0;
}

为什么会混淆

很多人认为”Java对象是引用传递”,是因为混淆了两个概念:

概念 含义
引用类型 Java中对象变量存储的是引用(地址)
引用传递 传参时传递变量本身的别名

Java的对象变量确实是引用类型,但传参时传递的是引用的值(副本),所以仍然是值传递

总结表格

场景 Java C++
基本类型传参 值传递 值传递
对象传参 传递引用的副本(值传递) 默认值传递,可用&引用传递
能否在函数内让外部变量指向新对象 不能 引用传递可以
swap函数能否直接交换 不能 引用传递可以

一句话总结

  • C++:默认值传递,加&变成引用传递
  • Java:永远是值传递,只不过对象变量的”值”是引用(地址)

记住这个区分:

  • 传递方式说的是:传参时是复制值还是传递别名
  • 引用类型说的是:变量存储的是地址而非实际数据