这道面试题是这样子的:
public class Example {
String str = "good";
char[] ch ={'a','b','c'};
public static void main(String[] args) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.print(ex.ch);
}
public void change(String str,char ch[]){
str = "test ok";
ch[0] = 'g';
}
}
先公布答案再来分析为什么是这个结果。
程序输出结果是:good and gbc
已经有很多网友从String的不可变性以及Java的值传递等进行了解释,这里直接通过看画出程序执行过程中的JVM内容情况,来解释为什么str没改变而ch改变了。
-
执行Example ex = new Example(); 后没有执行方法change之前的内存情况如下图:
[外链图片转存失败(img-h5jrB4MP-1564044090325)(https://s2.ax1x.com/2019/07/25/eeMKdf.png)]
-
在执行ex.change(ex.str,ex.ch)后,没有执行 str="test ok"之前时内存情况如下:

-
在执行完change方法中ch[0]='g’后,方法没有退出之前,内存情况如下:

-
在调用完change方法后,执行输出语句时,内存情况如下:

注:JDK1.7后运行时常量池位于堆中,但是不影响最终的输出。
这道面试题的另一个版本可能是:
public class Example {
String str = new String("good");
char[] ch ={'a','b','c'};
public static void main(String[] args) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.print(ex.str + " and ");
System.out.print(ex.ch);
}
public void change(String str,char ch[]){
str = "test ok";
ch[0] = 'g';
}
}
唯一区别在于程序第二行创建str时用的是new String(“good”)。
程序的输出结果仍然是:good and gbc
内存情况的区别在于:

也就是用new String(“good”)的方式创建str时,是在常量池中创建了“good”的字符串常量(类加载时),并且在堆中分配了一块空间保存String对象,内容是“good"(运行时).
如果修改下change中代码,将str = "test ok"修改为this.str = “test ok”,则程序输出结果为:test ok and gbc
原因是:
-
this返回的是当前调用对象的引用,其实也就是ex的引用,所以this.str就等价于ex.str。
-
注意change方法的两个参数str与ch都只是形参,是局部变量,在方法调用返回后局部变量的生命周期就结束了。
既然谈到了字符串,就看下另一段程序的输出结果:
public class TestString {
public static void main(String[] args) {
String a = "张三";
String b = "张";
String c = "三";
String d = b + c;
System.out.println(a == d);
String e = "张" + "三";
String f = "张" + c;
System.out.println(a == e);
System.out.println(a == f);
System.out.println(d == f);
}
}
输出结果:
false
true
false
false
这是因为这段代码在编译时即可确认e是字符串常量“张三”,而对于d或f,因为使用“+”拼接字符串引用变量,实际在运行时会在底层调用new StringBuilder().append()方法实现字符串拼接,所以每次拼接都会创建StringBuilder的临时变量,存放于Java堆中。每次都是新创建的StringBuilder对象,其地址自然不一样。
对TestString.class反编译可以看到:

String e在编译时即指向了与a同样的字符串常量。
至于+号拼接字符串引用变量底层调用new StringBuilder().append()可以通过javap命令,查看程序运行时的汇编指令:
javap -c -l TestString

699



被折叠的 条评论
为什么被折叠?



