这是Java中的一个经典问题。在 stackoverflow 中提出很多相似的问题,也有很多不正确、不完整的解答。想简单点,其实它很简单,但是你更进一步思考,你又会很迷惑。

1、一个有趣但又让人困惑的代码片段

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++中的代码如下:
void change(string &x) {
x = "cd";
}
int main(){
string x = "ab";
change(x);
cout << x << endl;
}输出的值为   cd ;

2、常见的让人迷惑的问

x存储了堆中ab”字符串的引用.所以,当x作为参数传递给方法change()时,x仍然指向堆中的字符串“ab”,如下图所示:

java 系统参数国际化 java的参数是什么_Java

由于Java中是值传递——x的值是“ab”的引用,当change()方法被调用后,创建了一个字符串“cd”对象,并且x现在指向了“cd”,当前状态如下图所示:

java 系统参数国际化 java的参数是什么_Java

这看上去是一个相当合理的解释。他们明确的知道java是值传递。但是,他们错在了哪里?

3、那段代码真正做了什么事?

以上的解释有几个误区。为了更容易了理解该问题,看完下面的内容是不错的选择。

当字符串“ab”被创建后 ,Java分配字符串对象需要的存储空间。然后将该字符串对象指定给变量x,实际上指定的是对象的引用(该引用是存储对象的内存的地址)。

变量x包含了字符串对象的引用。x不是引用本身,而是一个存储了引用(内存地址)的变量。

Java中只有值传递。当x作为参数传递给change()方法时,实际上传递的是x值得一个拷贝。方法change()创建了另一个字符串对象“cd”(有一个不同的引用——内存地址)。此时是变量x改变了它的引用,而不是变量x本身。下图展示了真正所做的事

java 系统参数国际化 java的参数是什么_Java

4、错误的解释

从以上代码片段中提出的问题跟String的不变性没有任何关系,即是换做StringBuiler也是一样的。关键点是变量存储了对象的引用(内存地址),但不是自己的引用(内存地址)。

5、解决方案

如果我们真的想要改变对象的值,首先,保证对象是可改变的(比如:StringBuiler);其次没有新的对象创建并赋值给参数(因为Java是值传递)。

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");
}

补充:上文第3点讲到了代码真正做的事,开始个人了很久也感觉还是模模糊糊的,后来经一位前辈的指点才算是彻底明白了(在此感谢cracker.li的帮助),以下用结合幅图来阐述自己的理解。为了便于理解在方法change(x)时,写了一句x' = x ;

java 系统参数国际化 java的参数是什么_Java

在上图中左下面表示“栈”,代码 x=“ab” ; 让x指向堆中字符串“ab”。调用方法change()时,创建了一个x的副本x' 。在未改变x'时,x'一直指向字符串“ab”。

java 系统参数国际化 java的参数是什么_Java

change()方法中执行代码x="cd",此时x’断开到“ab”的指向(引用地址改变),x‘指向"cd"(因为Java只有值传递)。

在此,x‘相当于一个匿名对象。由于Java只有值传递,所以在 x'=x ; 的时候,他们任何变量的内容的改变就互不影响了。