String为什么不可变?
1、什么是不可变?
java角度来讲就是说成final的。
String不可变如下图:
假设给字符串s赋值为abcd,第二次重新赋值为abcdef,这时候并不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
![](https://img.haomeiwen.com/i4582242/8edf6183dde7597d.png)
2、String为毛不可变?
看源码的前3行你就懂了。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
1、首先String类是用final修饰的,这说明String不可继承
2、其次String类的主力成员字段value是个char[]数组,而且是用final修饰的,final修饰的字段创建后就不可变。
注意:虽然value确实是final的,不可变了,但也只是value这个引用地址不可变,挡不住Array数组是可变的事实啊。
![](http://upload-images.jianshu.io/upload_images/4582242-31caadfb23dbfa12.png)
也就是说Array变量只是stack上的一个引用,数组的本体结构在堆,String类的value属性用final修饰,只是说stack里的这个叫value的引用地址不可变,没有说堆里array本身数据不可变,如下的例子:
final int[] value = {1, 2, 3};
int[] value2 = {11, 22, 33};
value = value2; //编译报错,value用final修饰,指向的地址不可变
但如下是可以的
final int[] value = {1, 2, 3};
value[0] = 11; //这时候数组里已经变成{11, 2, 3}了
这时候明白了吧?
String不可变的关键不是一个final不可被继承,而是关键在于底层实现,value的修饰符private作用都比final大,再加上类是final的防止继承,避免被破坏。
3、不可变有什么好处呢?
两个字:安全
//不可变的String
public static String appendStr(String str) {
str += "abc";
return str;
}
public static StringBuilder appendSb(StringBuilder sb) {
return sb.append("def");
}
public static void main(String[] args) {
//String做参数
String s1 = new String("abc");
String s2 = Demo2.appendStr(s1);
System.out.println("-------------" + s1.toString());
//StringBuilder做参数
StringBuilder sb = new StringBuilder("abc");
StringBuilder sb2 = Demo2.appendSb(sb);
System.out.println("-------------" + sb.toString());
}
结果:
-------------abc
-------------abcdef
可以发现String的值没有改变,这也正是他安全所在。
再看下载这个例子:
public static void main(String[] args) {
HashSet<StringBuilder> hs = new HashSet<StringBuilder>();
StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abc123");
hs.add(sb1);
hs.add(sb2); //这时候HashSet里是{"abc", "abc123"}
System.out.println(hs.toString());
StringBuilder sb3 = sb1;
sb3.append("123"); //这时候HashSet里是{"abc123", "abc123"}
System.out.println(hs.toString());
}
结果:
[abc, abc123]
[abc123, abc123]
StringBuilder变量sb1和sb2分别指向了堆内存的字面量“abc”和“def”,把他们都插入一个HashSet,到这步没毛病。但后面我把变量sb3也指向sb1的地址,在改变sb3的值,因为StringBuilder没有不可变的保护。sb3直接在原先“abc”的地址上改,导致sb1的值也变了。这时候HashSet上就出现了两个相等的键值“abc123”。破坏了HashSet键值的唯一性,所以千万不要用可变类型做HashMap和HashSet键值
还有就是多个线程同时读取一个资源,是不会引发竟态条件的。只有对资源做写操作才会有危险,永不可变对象不能被写,所以线程安全。
最后别忘了String自带常量池的属性,如下
![](http://upload-images.jianshu.io/upload_images/4582242-ef22fae568cf01c8.png)
堆内存只会有一块空间。节省内存空间,提高效率。
若有兴趣,欢迎来加入群,【Java初学者学习交流群】:458430385,此群有Java开发人员、UI设计人员和前端工程师。有问必答,共同探讨学习,一起进步!
欢迎关注我的微信公众号【Java码农社区】,会定时推送各种干货:
![](http://upload-images.jianshu.io/upload_images/4582242-ca4a357ae859b1aa.jpg)