可变类型与不可变类型
不可变数据类型: 当该数据类型的变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称为不可变数据类型。如java中基本数据类型以及String类型
String S="abc";
s+="def";
当执行第一条语句后,S指向“abc”所在的内存地址0x01,执行完第二句之后S的值变为“abcdef”,此时S指向的对象所在的内存地址也变成了0x02;
可变数据类型 :当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称为可变数据类型,当可变数据类型改变时它实际上是更改了内存中的内容。如StringBuilder,List,Set,Map。
StringBuilder S=new StringBuilder("a");
S.append("b");
当S的值发生改变时,始终指向0x03这个地址。
优点
可变:由于对不可变数据类型进行修改会产生大量的临时拷贝,很占用空间,相比之下可变数据类型可以将拷贝最少化从而提高效率。
不可变:不可变对象是线程安全的,在线程之间可以相互共享;存在对一个对象的多次引用时,一般采用不可变类型。
String类型的不可变
首先需要补充一个容易混淆的知识点:当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证变量所引用的地址不会改变,即一直引用同一个对象,但这个对象本身可以发生改变。
String类的两个主要成员变量:
private final char[] value;//用于存储字符串
private int hash; // Default to 0
其中value是变量,指向的是一个字符数组,字符串中的字符就是用这个value变量存储起来的,并且用final修饰,也就是说value一旦赋予初始值之后,value指向的地址就不能再改变了。但地址对应的内容是可以改变的。既然数组内容可变,那为什么String对象是不可变的呢?
一个String对象被创建以后,包含这个对象中的字符串序列是不可改变的。
String a=”abc”;
String a=”bc”;
这里a变量发生了变化是因为a所指向的对象发生改变,“abc”对象本身没有改变。图中,指向1是可变的,指向2不可变。0x20实际上就是value指向的数组的地址,而value是由final修饰因此value指向的引用地址(0x20)不可变。但是0x20存储的内容是可以发生变化的。只是由于String类没有提供修改数组内容的方法所以造成String类型对象不可变。然而在StringBuilder中是提供了相应的方法让我们去修改value指向的数组的元素,这也是StringBuilder的字符串序列可变的原因。
为什么String类被设计成不可变?
String不可变的原因包括设计考虑,效率优化 ,以及安全性这三大方面。
常量池的需要: 常量池的设计是为了优化性能,减少重复字符串的存储,而如果String对象的改变,会引起常量池的改变造成很多逻辑错误,增加损耗。
String S1="gcc";
String S2="gcc";
String S3="gcc";
由于S1、S2、S3全部指向常量池的同一个对象,如果字符串可变,修改S1指向的对象修改为“cc”,那么导致S2、S3全部被修改,这显然不符合编码逻辑。
允许String对象缓存HashCode:Java中String对象的哈希码被频繁地使用,字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存。
安全性:String被许多的Java类(库)用来当做参数,例如网络连接地址URL、文件路径path,还有反射机制所需要的String参数等,,假若String不是固定不变的,将会引起各种安全隐患。
线程安全:因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
String,StringBuffer, StringBuilder的区别是什么?
可变与不可变:String类不可变;StringBuilder与StringBuffer这两种对象都是可变的。
是否线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuilder是非线程安全的。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。