这在Java中也算是一个经典的问题了
1有趣的代码
首先我们来看一段简单的Java代码,并猜一猜这段代码会输出什么?
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";
}
然后来看看C++的代码,同样看看会输出什么结果
void change(string &x) {
x = "cd";
}
int main(){
string x = "ab";
change(x);
cout << x << endl;
}
很简单,第一段的Java代码输出结果是ab,而第二段的C++代码输出的是cd,你答对了吗?
2令人困惑的问题
那么为什么会是这样呢?
要清楚的是x实际存储的是指向堆heap中“ab”字符串的引用(reference),所以当x作为一个参数传递到方法change()时,它仍旧指向堆中的字符串”ab“,如下图所示:
因为Java是值传递(pass-by-value)的,所以x的值就是指向字符串"ab"的引用,当方法change()被触发之后,会生成一个新的对象"cd",那么这时候x就指向了这个新对象,如下图所示:
这看上去似乎是一个非常合理的解释,许多人也非常清楚Java是值传递的,那么这个解释到底哪里有问题呢?
3代码实际上做了什么呢
上面的解释确实是有些问题的,为了搞清楚这些问题,我们就把这个过程好好地走一遍
当创建字符串“ab”的时候,Java会分配相应的内存来存储该对象;然后,该对象被赋给了变量x,这时候变量x就被赋予了引用从而指向了对象——字符串“ab”,该引用也就是创建字符串"ab"时分配的内存地址
变量x包含了指向字符串对象的引用,但是变量x自身却并不是一个引用!它是存储了一个引用(内存地址)的变量。
Java是仅有值传递(pass-by-value ONLY),也就是说当你把变量x作为参数传递给方法change()时,实际传递的是变量x的值(引用)的一份拷贝。方法change()生成了一个新的对象"cd",也就是有了一个完全不同的引用,所以实际上就是变量x改变了指向"cd"的引用,而不是引用本身。请看下图所示
4错误的解释
5如何解决这个问题
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");
}
简单总结以下:值传递,和地址传递比较起来是完全不同的,像上面的C++代码的地址传递比较好理解,因为传递的就是这个变量本身,所以变量所指的值确实是改变了;而值传递则相当于原变量的一个复制品,所以对复制品的操作不会对原来的变量产生任何影响。