1. 引言
在方法调用中,参数传递主要有两种方式:值传递(Pass-by-Value) 和 引用传递(Pass-by-Reference)。不同编程语言对这两种机制的实现各不相同。⚠️但需要明确一点:Java 中所有参数传递都是严格意义上的值传递。
本文将深入剖析 Java 在处理不同类型参数时的具体行为,帮你彻底搞懂“为什么对象能被修改,基本类型却不能”,避免日常开发中踩坑。
2. 值传递 vs 引用传递
常见的参数传递机制有以下几种:
- 值传递(Pass-by-Value)
- 引用传递(Pass-by-Reference)
- 结果传递(Pass-by-Result)
- 值-结果传递(Pass-by-Value-Result)
- 名称传递(Pass-by-Name)
现代主流语言中最常见的是前两种。我们重点对比前两者:
2.1 值传递(Pass-by-Value)
✅ 调用方和被调用方法操作的是两个独立变量,彼此是副本关系。
❌ 对形参的任何修改,不会影响原始变量。
换句话说:传的是实参的“拷贝”。即使你在方法内部改得天花乱坠,原变量纹丝不动。
2.2 引用传递(Pass-by-Reference)
✅ 调用方和被调用方法操作的是同一个变量。
✅ 方法内对参数的修改会直接反映到原始变量上。
本质上,传递的是变量的内存地址(或句柄),双方指向同一块数据区域。
📌 注意:Java 并不支持这种模式。别被某些“Java 是引用传递”的错误说法误导了!
3. Java 中的参数传递机制
Java 的核心概念是 值(value) 和 引用(reference):
- ✅ 基本类型变量:直接存储值,放在栈上
- ✅ 引用类型变量:存储的是对象的引用(即地址),也放在栈上;实际对象在堆中
⚠️关键点来了:Java 所有参数传递都是值传递。
也就是说,无论是基本类型还是引用类型,传的都是“拷贝”:
- 基本类型:拷贝的是值本身
- 引用类型:拷贝的是“引用”(即地址),不是对象本身
这个“引用的拷贝”仍然指向堆中同一个对象,所以你可以在方法里修改对象内容 —— 但这不等于“引用传递”!
下面我们用代码实例来验证。
3.1 基本类型的值传递
Java 有 8 种基本类型(int
, boolean
, double
等)。它们的变量直接存值在栈上。
传参时,JVM 会在栈上为形参分配新空间,把实参的值复制过去。方法结束后,形参随栈帧销毁。
来看个简单例子:
public class PrimitivesUnitTest {
@Test
public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
int x = 1;
int y = 2;
// 修改前
assertEquals(x, 1);
assertEquals(y, 2);
modify(x, y);
// 修改后,原始值不变
assertEquals(x, 1);
assertEquals(y, 2);
}
public static void modify(int x1, int y1) {
x1 = 5;
y1 = 10;
}
}
📌 内存过程解析:
x
和y
在栈上创建,值为 1 和 2- 调用
modify()
时,x1
和y1
是x
、y
的副本,也位于栈上但地址不同 modify()
内修改x1
、y1
,只影响副本,不影响原始变量
3.2 引用类型的“值传递”
对象本身存储在堆中,栈上只存一个指向它的引用变量。
当传递对象时,传的是“引用的副本” —— 两个引用(实参和形参)指向堆中同一个对象。
所以:
- ✅ 修改对象内部状态(如字段) → 原对象可见
- ❌ 给形参重新赋值(指向新对象) → 不影响原引用
来看代码:
public class NonPrimitivesUnitTest {
@Test
public void whenModifyingObjects_thenOriginalObjectChanged() {
Foo a = new Foo(1);
Foo b = new Foo(1);
// 修改前
assertEquals(a.num, 1);
assertEquals(b.num, 1);
modify(a, b);
// 修改后:a.num 变了,b.num 没变
assertEquals(a.num, 2);
assertEquals(b.num, 1);
}
public static void modify(Foo a1, Foo b1) {
a1.num++; // 修改对象内容 → 原对象受影响
b1 = new Foo(1); // b1 指向新对象 → 原 b 不受影响
b1.num++;
}
}
class Foo {
public int num;
public Foo(int num) {
this.num = num;
}
}
📌 内存演变过程:
初始状态:a
和 b
分别指向堆中两个独立的 Foo
实例
调用 modify() 后:a1
和 b1
是 a
、b
的引用副本,仍指向原对象
modify() 执行中:
a1.num++
→ 修改的是a
所指对象,所以a.num
变成 2b1 = new Foo(1)
→b1
指向新对象,与原b
脱离关系
💡 简单粗暴总结:
“Java 是值传递” + “引用也是值” = 你传的是“引用的拷贝”
所以能改内容,但改不了“谁指向谁”
4. 结论
Java 的参数传递机制统一且简单:永远是值传递。
区别在于传递的“值”是什么:
类型 | 传递的内容 | 能否修改原始变量 | 能否修改对象内容 |
---|---|---|---|
基本类型 | 值的拷贝 | ❌ | ❌ |
引用类型 | 引用的拷贝(地址) | ❌ | ✅ |
✅ 正确认知:
“对象能被修改” 不是因为“引用传递”,而是因为“多个引用指向同一个堆对象”。
⚠️ 常见误区:
不要说“Java 基本类型是值传递,对象是引用传递” —— 这是错误的!统一都是值传递,只是值的类型不同。
文中所有代码示例均可在 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-lang-oop-others