java 中String到底是值传递还是引用传递的问题 | 您所在的位置:网站首页 › 引用传递和值传递的区别java › java 中String到底是值传递还是引用传递的问题 |
为什么String是不可变的:
Java中的String为什么是不可变的? -- String源码分析_昨夜星辰的博客-CSDN博客_string为什么是不可变的什么是不可变对象?众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。区分对象和对象的引用对于Java初学 最近看到一道关于string的面试题,就是下面这个例子,体验下: 1 2 3 4 5 6 7 8 9 10 11 12 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常量池里没有该字符串的话),我们可以验证一下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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相同的不一定是同一个对象。 2.String str = "" 和 new String()的区别首先明白一个事,java存在一个常量池,可以用来存储字符串常量。 1 创建的字符串变量在内存中的区别两者看似都是创建了一个字符串对象,但在内存中确是各有各的想法。 String str1= “abc”; 在编译期,JVM会去常量池来查找是否存在“abc”,如果不存在,就在常量池中开辟一个空间来存储“abc”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“abc”在常量池中的地址值。 String str2 = new String("abc") ;当使用String str=new String("abc");时,不管事先是否存在"abc",每次都会创建其新的对象。 2 String类的特性String类 是final修饰的,不可以被继承。 String类的底层是基于char数组的。 3 两个方面1)性能效率 String类被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。例如: String str = “hello"; str = str + "world“; 所以当上文str指向了一个String对象(内容为“hello”),然后对str进行“+”操作,str原来指向的对象并没有变,而是str又指向了另外一个对象(“hello world”),原来的对象还在内存中。 由此也可以看出,频繁的对String对象进行修改,会造成很大的内存开销。此时应该用StringBuffer或StringBuilder来代替String。 而new String()更加不适合,因为每一次创建对象都会调用构造器在堆中产生新的对象,性能低下且内存更加浪费。 2)安全性 对象都是只读的,所以多线程并发访问也不会有任何问题。 由于不可变,用来存储数据也是极为安全的。 |
CopyRight 2018-2019 实验室设备网 版权所有 |