为何调用方法获取的sb引用值不为null而是赋值前的内容?
这是个非常经典的Java参数传递问题,核心在于搞清楚Java里的值传递到底是怎么回事,尤其是对象类型的参数传递逻辑。咱们一步步拆解你的代码,就能明白为什么会出现这种情况:
首先先看你的完整代码:
public class Test{ void testRefs(String str, StringBuilder sb){ str = str + sb.toString(); sb.append(str); System.out.println(sb); str = null; sb = null; System.out.println(sb); } public static void main(String[] args){ String s = "aaa"; StringBuilder sb = new StringBuilder("bbb"); new Test().testRefs(s, sb); System.out.println("s="+s+" sb="+sb); } }
关键原理:Java的参数传递是值传递
很多人会误以为Java的对象是引用传递,但实际上Java只有值传递:
- 对于基本类型,传递的是变量的值的副本;
- 对于对象类型,传递的是对象引用的副本——也就是说,方法里的
sb参数和main方法里的sb变量,是两个独立的变量,但它们一开始指向同一个StringBuilder对象。
逐行分析代码执行流程
main方法初始化:
String s = "aaa"; StringBuilder sb = new StringBuilder("bbb");这里
sb变量指向一个内容为"bbb"的StringBuilder对象。调用
testRefs(s, sb):
此时Java会把main里s的引用副本、sb的引用副本,分别传递给方法的参数str和sb。现在:main.sb和testRefs.sb指向同一个StringBuilder对象;main.s和testRefs.str指向同一个String对象"aaa"。
执行
str = str + sb.toString();:
String是不可变类型,str + sb.toString()会创建一个新的String对象"aaabbb",然后方法内的str变量会指向这个新对象——但这和main里的s变量没有任何关系,因为main.s仍然指向原来的"aaa"。执行
sb.append(str);:
方法内的sb副本指向的是和main.sb同一个StringBuilder对象,所以append操作会直接修改这个对象的内容,变成"bbb" + "aaabbb" = "bbbaaabbb"。这时候main里的sb对象也会看到这个变化,因为它们指向的是同一个实例。执行
str = null; sb = null;:
这一步只是把方法内的两个局部变量(引用副本)赋值为null,让它们不再指向原来的对象。但main里的sb变量仍然牢牢指向那个已经被修改过的StringBuilder对象,所以这一步对外部完全没有影响。方法内的两次输出:
- 第一次
System.out.println(sb);输出的是修改后的对象内容bbbaaabbb; - 第二次
System.out.println(sb);输出null,因为此时方法内的sb已经被赋值为null了。
- 第一次
回到main方法的输出:
System.out.println("s="+s+" sb="+sb);会输出s=aaa sb=bbbaaabbb,原因是:s还是"aaa",因为方法内的str修改的是引用副本指向的新对象,和原变量无关;sb是修改后的"bbbaaabbb",因为之前的append操作修改了对象本身,而方法内的sb=null只是修改了副本引用,没有影响原对象。
总结一下核心要点
- 如果在方法内修改参数指向的对象的内容(比如
StringBuilder.append),外部会看到变化,因为参数和外部变量指向同一个对象; - 如果直接给参数变量赋值(比如
sb=null),只是让方法内的副本变量不再指向原对象,外部变量不受任何影响——这就是你疑惑的根本原因。
内容的提问来源于stack exchange,提问作者Michael

