一文带你搞清String是值传递还是引用传递?
String中的坑最近看到一道关于String的面试题,差点让我以为String是值传递,就是下面这个例子,体验下:public class Demo{public static void main(String[] args) {Demo d = new Demo();String str = "BEA";d.change(str);System.out.println(str);}void
看这个之前 可以先参考我的另一篇文章:
一文搞懂Java的值传递和引用传递
String中的坑
最近看到一道关于String的面试题,差点让我以为String是值传递,就是下面这个例子,体验下:
public class Demo{
public static void main(String[] args) {
Demo d = new Demo();
String str = "BEA";
d.change(str);
System.out.println(str);
}
void change(String s){
s= s.replace('A', 'E');
s = s.toLowerCase();
}
}
当时一看到这个题目,我第一反应就是输出”bee“,因为String是引用类型,其参数传递的方式就是引用传递,传递的是String的地址。可是答案让我的大吃一惊,“BEA”,str根本就没有发生变化!!
难道String是值传递?难道String是基本类型?
其实都不是,后来通过查阅相应资料发现,jvm在实例化字符串时会使用字符串常量池,把str作为参数传入change()方法。jvm复制了一份str变量,为了便于理解我们叫它str’。这个时候str和str’都指向字符串常量池中的“abc”。
当我们执行s = s.replace('A', 'E');
其实相当于执行了s = new String(s.replace('A', 'E'));
要理解上面这两段话,就要从java的底层结构说起了。java的内存模型大体分为 堆 和 栈 (细分还有方法区,和程序计数器等)。
1.基本类型的变量放在栈里;
2.封装类型中,对象放在堆里,对象的引用放在栈里。
java在方法传递参数时,是将变量复制一份,然后传入方法体去执行。
根据这些再细分一下jvm的执行过程
1.虚拟机在堆中开辟一块内存,并存值”BEA”。
2.虚拟机在栈中分配给str一个内存,内存中存的是1中的地址。(1指第一步)
3.虚拟机复制一份str,我们叫str’,str和str’内存不同,但存的值都是1的地址。
4.将str’传入方法体
5.方法体在堆中开辟一块内存,并存值”BEE”。
6.方法体在堆中再次开辟一块内存,并存值”bee”。
7.方法体将str’的值改变,存入5的内存地址。
8.方法结束,方法外打印str,由于str存的是1的地址,所有打印结果是”BEA”。
String的底层是一个不可变数据,所以每次给他赋新的值的时候都相当于新建了一个String对象(如果String常量池里没有该字符串的话),我们可以验证一下。
public class Demo{
public static void main(String[] args) {
Demo d = new Demo();
//通过比较str的hashCode来比较两个对象是否为同一对象
String str = "BEA";
System.out.println("第一次String的hashCode:"+str.hashCode());
str = "bee";
System.out.println("第二次String的hashCode:"+str.hashCode());
//StringBuilder来试一次
StringBuilder s = new StringBuilder("BEA");
System.out.println("第一次StringBuilder的hashCode:"+s.hashCode());
s.append('T');
System.out.println("第二次StringBuilder的hashCode:"+s.hashCode());
System.out.println("调用方法前的StringBuilder对象的值:"+s);
d.change(s);
System.out.println("调用方法后的StringBuilder对象的值:"+s);
}
void change(StringBuilder s){
s = s.append('S');
}
}
看看执行的结果~
tips: hashcode并不能判断是否为同一个对象,但是hashcode不同的话肯定不是同一个对象,hashcode相同的不一定是同一个对象。
其他理解1:
1.String是引用类型。所以是引用传递
但是String没有任何可以改变它状态的字段/方法,所有操作String的方法都是返回一个新的String对象,所以在函数里无论怎么改变String都是让它指向新的对象,从而对实参没有影响。
String是只读的。
2.字符串放在常量池里面,所以是值传递
其他理解2:
调用方法时发生了什么?参数传递基本上就是赋值操作。
第一个例子:基本类型
void foo(int value) {
value = 100;
}
foo(num); // num 没有被改变
第二个例子:没有提供改变自身方法的引用类型
void foo(String text) {
text = "windows";
}
foo(str); // str 也没有被改变
第三个例子:提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。
第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。
重点理解为什么,第三个例子和第四个例子结果不同?
下面是第三个例子的图解:
builder.append(“4”)之后
下面是第四个例子的图解:
builder = new StringBuilder(“ipad”); 之后
个人总结:
java都是“值传递”,关键看这个值是什么,简单变量就是复制了具体值,引用变量就是复制了地址呗。字符串虽然是引用类型,但是它是不可变的,当然不会被修改!
参考文章:
https://www.zhihu.com/question/31203609/answer/50992895
https://segmentfault.com/a/1190000020842508
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)