当字符串“沉默王二”被创建的时候,Java 会在内存中申请一小段空间,用来存储这个字符串对象。然后呢,把对象的引用指向了变量 x,也就是说,变量 x 实际上存储的是对象的引用(对象在内存中存储的地址)。
我相信大家对上面这一点(对象和对象引用)已经完全理解了。
关键的点来了。当变量 x 作为参数(实参)传递给 change() 方法时,实际上传递的是 x 的一个拷贝(形参)。在 change() 方法中,形参 x 起先引用的也是“沉默王二”这个对象,当执行 x = "沉默王三" 的时候,会在内存中创建新的字符串“沉默王三”,然后形参 x 不再引用“沉默王二”这个对象了,改为引用“沉默王三”这个对象了。但实参 x 呢?并没有发生任何的改变!就像下面这幅图一样。
public static void main (String [ ] args ) {
StringBuilder x = new StringBuilder ( "沉默王二" ) ;
change (x ) ;
System .out . println (x ) ;
}
public static void change (StringBuilder x ) {
x . delete ( 3 , 4 ) . append ( "三" ) ;
}
上述代码会输出“沉默王三”,但假如我们使用 new 关键字重新对形参 x 进行赋值,就无济于事。
public static void main (String [ ] args ) {
StringBuilder x = new StringBuilder ( "沉默王二" ) ;
change (x ) ;
System .out . println (x ) ;
}
public static void change (StringBuilder x ) {
x = new StringBuilder ( "沉默王三" ) ;
}
程序输出的结果仍然是“沉默王二”,原因其实和 String 一样, change() 方法在内存中创建了新的字符串“沉默王三”,然后形参 x 不再引用“沉默王二”这个对象,改为引用“沉默王三”这个对象了。但实参 x 并没有任何改变。
看到这,有些读者可能更疑惑了。 x = new StringBuilder("沉默王三") 不可以改变实参,而 x.delete(3,4).append("三") 却可以,为什么?为什么?为什么?为什么呢?
不要着急,我们来分析一下 delete() 方法的源码。
public AbstractStringBuilder delete ( int start , int end ) {
int len = end - start ;
if (len > 0 ) {
System . arraycopy (value , start +len , value , start , count -end ) ;
count -= len ;
}
return this ;
}
其中 value 是一个字符数组,用来存储字符序列;count 用来表示字符序列中实际有效的字符数量。
当 count -= len 执行之前,value 的字符内容为“沉默王二”,count 为 4。我是怎么知道的呢?通过 IDEA 的 debug 视图,截图为证。
当 count -= len 执行之后,value 的字符内容仍然为“沉默王二”,但 count 变成了 3。
当鼠标停留在 this 上时,此时的字符内容为“沉默王”,也就意味着 x 当前的字符内容为“沉默王”。同样的,当我们在 append() 方法上进行 debug 的时候,也可以观察到字符串发生变化的细节。