这是非常基础的的java问题。很多相同的问题在stackoverflow都被问,同时有很多不正确或者未完成的回答。这个问题很简单,但是你不好好想想,你会感到疑惑,使用你最好好好想想。

1.代码片段很有趣同时也会让人疑惑

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
String x = new String("ab");
change(x);
System.out.println(x);
}
public static void change(String x) {
x = "cd";
}

这将会输出ab

在C++中,这代码如下:

1
2
3
4
5
6
7
8
9
void change(string &x) {
x = "cd";
}
int main(){
string x = "ab";
change(x);
cout << x << endl;
}

但是输出是cd

2. 常见疑惑的问题

在堆中x存储的索引是指向字符串”ab”的。所以当x以参数传递给change()方法,在堆中它仍然指向”ab”,如下图:

 string pass by reference

因为,在java是通过值进行传递的,x的值指向”ab”。当 change()调用的时候,将会创建”cd”对象,同时x指向了”cd”。如图:

 string pass by reference

这看起来似乎是一个很合理的解释。java都是使用值传递,但是这错在哪了?

3. 这代码实际做什么?

上面的解释有几个错误。为了更加好明白,我们来简单走走这个过程。

当”ab”创建时候,java花费时间申请内存来储存对象。然后这个对象被赋予了x变量,这个变量就实际指向了这个对象。这个索引是存储这个对象内存的地址。

x变量包含了一个string对象的索引。x并没有自己的索引。它只是一个存储索引的变量(内存地址)。

java是通过值传递的。所以当x通过change()传递的时候,拷贝了x的值(一个索引)来传递。在change()方法中创建了另外一个对象”cd”并且它有不同的索引。这时是变量x改变了它的索引(b变成了”cd”),而不是索引自身。

过程可以用下图表示:

 string pass by reference

4. 错误的解释

这个问题来源于他的第一行代码使用了不可变的string。即使String换成StringBuilder,借输出结果还是一样的。最关键的是变量存储了索引,但是它没有索引自己。

5. 解决这个问题

如果我们确实需要改变对象的值,首先对象是可变的,比如: StringBuilder,其次我们需要确保没有新创建对象赋予传递参数,因为java本来就是通过值传递的。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
StringBuilder x = new StringBuilder("ab");
change(x);
System.out.println(x);
}
public static void change(StringBuilder x) {
x.delete(0, 2).append("cd");
}

参考文档