Java 只有值传递(Pass-by-Value)
要明确一个最核心、最根本的原则:Java 语言中,所有的参数传递方式都是值传递。

没有“引用传递”(Pass-by-Reference)。
为什么 String 看起来又像是“引用传递”呢?这就需要我们深入理解“值传递”和“引用”在 Java 中的具体含义了。
理解“值传递”和“引用”
为了彻底搞懂这个问题,我们必须先区分两个概念:
- 值:就是数据本身。
- 引用:可以理解为内存地址的“拷贝”或“别名”,它指向一个对象在堆内存中的实际位置。
在 Java 中,变量分为两种基本类型:

- 基本数据类型:如
int,double,char,boolean等,它们的变量中存储的就是值本身。 - 引用数据类型:如所有对象(
String,ArrayList, 自定义类等)、数组,它们的变量中存储的不是对象本身,而是该对象在堆内存中的地址(即一个引用)。
两种数据类型的传值表现
理解了上面的概念后,我们来看两种数据类型在方法调用时是如何“传值”的。
A. 传递基本数据类型
当传递一个基本数据类型(如 int)时,传递的是值的拷贝。
示例代码:
public class Main {
public static void main(String[] args) {
int num = 10;
System.out.println("调用方法前, main方法中的 num = " + num);
changePrimitive(num);
System.out.println("调用方法后, main方法中的 num = " + num);
}
public static void changePrimitive(int value) {
// 这里的 value 是 main方法中 num 的一个值的拷贝
value = 20; // 修改的是这个拷贝,不影响 main方法中的原始 num
System.out.println("方法内部, value = " + value);
}
}
执行结果:

调用方法前, main方法中的 num = 10
方法内部, value = 20
调用方法后, main方法中的 num = 10
内存分析:
main方法中创建int num = 10,num变量在栈内存中,值为10。- 调用
changePrimitive(num)时,将num的值10拷贝一份,传递给changePrimitive方法的value参数。 - 在
changePrimitive方法内部,value是一个全新的、独立的变量,它有自己的内存空间,我们修改value = 20,只是修改了这个拷贝的值。 main方法中的num变量从未被触及,所以它的值依然是10。
这非常直观,也很好理解。
B. 传递引用数据类型(如 String)
当传递一个引用数据类型(如 String)时,传递的是引用(地址)的拷贝。
这才是问题的核心和容易混淆的地方。
示例代码:
public class Main {
public static void main(String[] args) {
String text = "Hello";
System.out.println("调用方法前, main方法中的 text = " + text);
System.out.println("调用方法前, text的地址 = " + System.identityHashCode(text));
changeReference(text);
System.out.println("调用方法后, main方法中的 text = " + text);
System.out.println("调用方法后, text的地址 = " + System.identityHashCode(text));
}
public static void changeReference(String str) {
System.out.println("方法内部, 初始的 str = " + str);
System.out.println("方法内部, str的地址 = " + System.identityHashCode(str));
// 尝试修改 str 指向的对象
str = "World";
System.out.println("方法内部, 修改后的 str = " + str);
System.out.println("方法内部, 修改后str的地址 = " + System.identityHashCode(str));
}
}
执行结果:
调用方法前, main方法中的 text = Hello
调用方法前, text的地址 = 460141958 // 地址A
方法内部, 初始的 str = Hello
方法内部, str的地址 = 460141958 // 地址A (和main中的text地址相同)
方法内部, 修改后的 str = World
方法内部, 修改后str的地址 = 1163157884 // 地址B (和main中的text地址不同)
调用方法后, main方法中的 text = Hello
调用方法后, text的地址 = 460141958 // 地址A
内存分析(关键步骤):
-
main方法中:String text = "Hello";- 在堆内存中创建了一个内容为
"Hello"的String对象。 - 在栈内存中,变量
text存储了这个对象的引用(地址A)。
-
调用
changeReference(text)时:- 发生了值传递,传递的不是
text本身,也不是"Hello"对象,而是text中存储的引用(地址A)的一个拷贝。 - 这个拷贝被赋值给了
changeReference方法的参数str。 main方法中的text和changeReference方法中的str,都指向了堆内存中同一个"Hello"对象(地址A),它们是两个不同的变量,但持有相同的引用。
- 发生了值传递,传递的不是
-
在
changeReference方法内部:str = "World";- 这一步不是修改了
str所指向的对象的内容,而是让str这个引用变量指向了一个全新的对象! - JVM 会在堆内存中创建一个新的
String对象,内容为"World"(地址B)。 - 将
str变量的值从原来的地址A更改为新对象的地址B。 - 这个操作只影响了
changeReference方法内部的局部变量str。main方法中的text变量依然指向原来的"Hello"对象(地址A),所以它的值没有改变。
在 changeReference 方法内部,我们无法通过 str 来修改 main 方法中 text 所指向的那个 String 对象,因为我们修改的是 str 这个“地址拷贝”本身,让它指向了别处,而没有通过这个地址去修改原始对象的内容。
String 的特殊性:不可变性
上面的分析已经解释了为什么 String 在方法中不会被修改,而 String 的一个核心特性——不可变性——更是从根源上杜绝了这种情况。
- 不可变性:意味着一旦一个
String对象被创建,它的内容就不能被改变。 - 任何看起来像是修改
String的操作(如str = "World"),实际上都是:- 创建了一个新的
String对象。 - 让引用变量指向这个新对象。
- 创建了一个新的
这与我们上面分析的内存过程完全吻合,正是因为 String 是不可变的,所以我们才无法写一个方法,让它修改传入的 String 对象的内容。
对比一个可变对象(如 StringBuilder)来加深理解:
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
System.out.println("调用方法前, sb = " + sb);
changeMutable(sb);
System.out.println("调用方法后, sb = " + sb);
}
public static void changeMutable StringBuilder s) {
// s 指向和 main 中 sb 相同的对象
s.append(" World"); // 通过引用 s,修改了它所指向的对象的内容
}
}
执行结果:
调用方法前, sb = Hello
调用方法后, sb = Hello World
分析:
main中的sb和changeMutable中的s指向同一个StringBuilder对象。s.append(" World")是通过引用s,找到了那个对象,并调用了它的append方法。StringBuilder是可变的,append方法会直接修改这个对象的内容。- 因为
main中的sb也指向同一个对象,所以它能看到内容的变化。
这个例子清晰地展示了:即使是引用传递(实际上是引用地址的拷贝),如果对象是可变的,我们就可以通过这个拷贝去修改原始对象。
| 特性 | 描述 |
|---|---|
| Java 传值机制 | 永远是值传递。 |
| 传基本类型 | 传递的是值的拷贝,方法内修改不影响外部变量。 |
| 传引用类型 (如 String) | 传递的是引用(地址)的拷贝,方法内可以通过这个引用访问到原始对象。 |
| String 的特殊性 | 不可变性:决定了你无法通过任何方法修改一个已存在的 String 对象的内容。结合传值机制:当你尝试在方法中“修改” String 变量时,你实际上只是让方法内的引用指向了一个新对象,而外部的原始引用保持不变。 |
| 关键区别 | 能否在外部看到变化,取决于: 对象是否可变(如 StringBuilder 可变,String 不可变)。在方法内是修改了对象内容,还是修改了引用本身。 |
当你把一个 String 对象传给一个方法时,你其实是给了这个方法一个“寻宝图”的复印件,这个复印件能帮你找到宝藏(原始对象)。String 的宝藏本身是封印的,无法修改的,你更常见的操作不是去修改宝藏,而是把这张寻宝图扔掉,换成一张指向全新宝藏的地图,这当然不会影响原来那张寻宝图的所有者。
