最近看到一道关于String的面试题,差点让我觉得String是值传递,就是下面这个例子,体验下:<!-- more -->java
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是基本类型?segmentfault
其实都不是,后来经过查阅相应资料发现,jvm在实例化字符串时会使用字符串常量池,把str做为参数传入change()方法。jvm复制了一份str变量,为了便于理解咱们叫它str'。这个时候str和str'都指向字符串常量池中的“abc”。app
当咱们执行s = s.replace('A', 'E');
其实至关于执行了s = new String(s.replace('A', 'E'));
jvm
要理解上面这两段话,就要从java的底层结构提及了。java的内存模型大致分为 堆 和 栈 (细分还有方法区,和程序计数器等)。ui
1.基本类型的变量放在栈里;
2.封装类型中,对象放在堆里,对象的引用放在栈里。
<font color=red>java在方法传递参数时,是将变量复制一份,而后传入方法体去执行。</font>spa
根据这些再细分一下jvm的执行过程code
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相同的不必定是同一个对象。blog
关注我不会让你失望哟~