核心概念:Java 只有值传递
最重要的一点必须明确:Java 中只有值传递。

“值传递”意味着,在调用一个方法时,你传递给方法的是参数值的一个拷贝,而不是参数本身。
- 对于基本数据类型:拷贝的是值本身,方法内对这个拷贝的修改,不会影响到方法外的原始变量。
- 对于引用数据类型:拷贝的是引用地址值,方法内可以通过这个地址访问到堆中的同一个对象,但方法内对这个地址本身的修改(例如让它指向一个新对象),不会影响到方法外的原始引用。
String 是引用类型,但它很特殊
String 在 Java 中是一个不可变类,这意味着一旦一个 String 对象被创建,它的内容就不能被改变,任何对 String 的操作,concat()、replace()、substring() 等,都不会修改原始的 String 对象,而是会返回一个新的 String 对象。
这个特性是理解 String 在方法传递中行为的关键。
深入分析 String 的值传递行为
我们通过几个经典的代码示例来一步步拆解。
示例 1:方法内修改 String 的内容(实际上是创建新对象)
public class StringPassing {
public static void main(String[] args) {
String originalStr = "Hello";
System.out.println("调用方法前 originalStr: " + originalStr); // 输出: Hello
modifyString(originalStr);
System.out.println("调用方法后 originalStr: " + originalStr); // 输出: ???
}
public static void modifyString(String str) {
// str 是 main 方法中 originalStr 的一个引用拷贝
// str 和 originalStr 指向堆中同一个 "Hello" 对象
System.out.println("方法内 str 初始值: " + str); // 输出: Hello
// str.concat(" World") 不会修改 "Hello" 这个对象
// 它会创建一个新的 String 对象 "Hello World",并让 str 指向这个新对象
str = str.concat(" World");
System.out.println("方法内 str 修改后: " + str); // 输出: Hello World
}
}
执行结果:
调用方法前 originalStr: Hello
方法内 str 初始值: Hello
方法内 str 修改后: Hello World
调用方法后 originalStr: Hello
图解分析:
-
main方法开始:- 在栈中创建一个引用变量
originalStr。 - 在字符串常量池(属于堆的特殊区域)中创建
String对象"Hello"。 originalStr指向"Hello"。
main 栈: 堆 (字符串常量池) +-----------+ +----------------+ |originalStr| ----> | "Hello" | +-----------+ +----------------+ - 在栈中创建一个引用变量
-
调用
modifyString(originalStr):- 值传递发生:
originalStr中存储的引用地址值被拷贝了一份。 - 这个拷贝的地址值被赋给了
modifyString方法的参数str。 originalStr和str都指向同一个"Hello"对象。
main 栈: modifyString 栈: 堆 (字符串常量池) +-----------+ +-----------+ +----------------+ |originalStr| ----> | str | ----> | "Hello" | +-----------+ +-----------+ +----------------+ - 值传递发生:
-
modifyString方法内部执行str = str.concat(" World");:str.concat(" World")方法被调用,由于String不可变,它不会修改"Hello"。- 它会在字符串常量池中创建一个全新的
String对象为"Hello World"。 - 赋值语句
str = ...会将str这个引用变量重新指向这个新创建的"Hello World"对象。 - 关键点:这个操作只改变了
modifyString方法栈中局部变量str的指向,它对main方法中的originalStr没有任何影响。
main 栈: modifyString 栈: 堆 (字符串常量池) +-----------+ +-----------+ +----------------+ |originalStr| ----> | str | | "Hello" | +-----------+ +-----+-----+ +----------------+ | | | +-----> | "Hello World" | | +----------------+ +--------------------------------+ -
modifyString方法结束:str这个局部变量随着方法的结束而被销毁。originalStr仍然指向那个从未改变的"Hello"对象。
main 栈: 堆 (字符串常量池) +-----------+ +----------------+ |originalStr| ----> | "Hello" | +-----------+ +----------------+
在方法内尝试通过 str 修改 String 的内容,实际上只是让 str 指向了一个新对象,原始的 originalStr 指向的对象和引用都保持不变。
示例 2:通过 StringBuilder 修改(可变对象对比)
为了更好地理解 String 的不可变性,我们来看一个可变的对象,StringBuilder。
public class StringBuilderPassing {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
System.out.println("调用方法前 sb: " + sb); // 输出: Hello
modifyStringBuilder(sb);
System.out.println("调用方法后 sb: " + sb); // 输出: Hello World!
}
public static void modifyStringBuilder(StringBuilder strBuilder) {
System.out.println("方法内 strBuilder 初始值: " + strBuilder); // 输出: Hello
// strBuilder.append() 会直接修改 strBuilder 指向的对象
strBuilder.append(" World!");
System.out.println("方法内 strBuilder 修改后: " + strBuilder); // 输出: Hello World!
}
}
执行结果:
调用方法前 sb: Hello
方法内 strBuilder 初始值: Hello
方法内 strBuilder 修改后: Hello World!
调用方法后 sb: Hello World!
图解分析:
-
main方法开始:sb指向堆中的StringBuilder对象。
main 栈: 堆 +-----------+ +---------------------------+ | sb | ----> | StringBuilder (value="Hello") | +-----------+ +---------------------------+ -
调用
modifyStringBuilder(sb):sb的引用地址被拷贝一份,赋给strBuilder。sb和strBuilder指向同一个StringBuilder对象。
main 栈: modifyStringBuilder 栈: 堆 +-----------+ +----------------+ +---------------------------+ | sb | ----> | strBuilder | ----> | StringBuilder (value="Hello") | +-----------+ +----------------+ +---------------------------+ -
modifyStringBuilder方法内部执行strBuilder.append(" World!");:StringBuilder是可变的。append()方法会直接修改strBuilder所指向的那个对象的内容。- 对象的值从
"Hello"变成了"Hello World!"。 - 因为
sb和strBuilder指向的是同一个对象,sb能看到这个变化。
main 栈: modifyStringBuilder 栈: 堆 +-----------+ +----------------+ +---------------------------+ | sb | ----> | strBuilder | ----> | StringBuilder (value="Hello World!") | +-----------+ +----------------+ +---------------------------+
结论对比:
String:方法内只能让引用指向新对象,无法修改旧对象。StringBuilder:方法内可以通过引用直接修改对象本身。
| 特性 | Java 传递方式 | String 的行为 |
StringBuilder 的行为 |
|---|---|---|---|
| 传递机制 | 值传递 | 传递引用地址的拷贝 | 传递引用地址的拷贝 |
| 对象可变性 | - | 不可变 | 可变 |
| 方法内修改 | - | 无法修改原始对象,只能让参数引用指向一个新对象,原始引用不受影响。 | 可以修改原始对象,因为方法内和原始引用指向同一个对象,修改是可见的。 |
| 核心原因 | - | String 的不可变性 + 值传递。 |
StringBuilder 的可变性 + 值传递。 |
一句话概括:
在 Java 中,String 作为参数传递时,传递的是其引用地址的拷贝,由于 String 对象本身不可变,方法内无法修改原始对象,只能创建新对象并让拷贝的引用指向它,因此原始 String 变量在方法调用后保持不变,这常常被误解为“引用传递”,但其本质仍然是“值传递”。
