为什么要把Java字符串设计为不可变的

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_36952611/article/details/72722628

String是Java中一个不可变的类,所以它一旦被实例化就无法被修改。不可变类一旦被创建就不可以被修改。本文将从内存、同步和数据结构相关知识简单说明一下将String设计为不可变类的好处。

(1)字符串池:

字符串池是方法区中一部分特殊存储。当一个字符串被创建的时候,首先会去字符串池中查找,如果找到,直接返回对该字符串的引用。

String str1 = "abcd";
String str2 = "abcd";

这里写图片描述
这两个创建的引用都指向了同一个内存地址,在大量使用的情况下可以节省空间提高效率。最重要的是如果String是可变的话那么修改str2就会使得str1的值也发生改变,这不是我们希望看到的。

(2)String为什么不可变

在JDK源码中String类是被final修饰的,这就说明了String类不可以被继承。String本质上是一个char数组,而且也是用final修饰的。

//JDK部分源码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

虽然value是不可变的,但也只是它的引用地址不可变,并不能改变它是一个Array数组的事实。也就是说虽然Array的引用地址不可变,但是Array中的数据是可以改变的。

final int[] value={1,2,3}  
int[] another={4,5,6};  
value=another;    //编译器报错,final不可变 
//因为value使用final修饰的,不允许我就value的引用指向另一个堆区
//但是如果直接修改里面的数值就是另外一回事了
final int[] value={1,2,3};  
value[2]=100;  //这时候数组里已经是{1,2,100}
//或者直接用反射直接修改
final int[] array={1,2,3};  
Array.set(array,2,100); //数组也被改成{1,2,100}  

(3)不可变有什么好处

最直接的好处就是安全。String被广泛应用在其他Java类中充当参数。比如网络连接、打开文件等操作。如果字符串可变,那么类似操作可能会导致安全问题。因为某个方法在调用连接操作的时候,它认为会连接到某个机器上,但实际上没有(其它引用同一字符串的值修改会直接导致该字符串中内容的修改)。可变字符串也可能导致反射安全问题,因为他的参数也是字符串。

    class Test{  
        public static void main(String[] args){  
            HashSet<StringBuilder> hs=new HashSet<StringBuilder>();  
            StringBuilder sb1=new StringBuilder("aaa");  
            StringBuilder sb2=new StringBuilder("aaabbb");  
            hs.add(sb1);  
            hs.add(sb2);    //这时候HashSet里是{"aaa","aaabbb"}  

            StringBuilder sb3=sb1;  
            sb3.append("bbb");  //这时候HashSet里是{"aaabbb","aaabbb"}  
            System.out.println(hs);  
        }  
    }  
    //Output:  
    //[aaabbb, aaabbb]  

StringBuilder型变量sb1和sb2分别指向了堆内的字面量”aaa”和”aaabbb”。把他们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先”aaa”的地址上改。导致sb1的值也变了。这时候,HashSet上就出现了两个相等的键值”aaabbb”。破坏了HashSet键值的唯一性。所以千万不要用可变类型做HashMap和HashSet键值。
不可变对象天生就是线程安全的。在并发的场景下,多个线程去读一个资源时,不会出现什么问题,但是多个线程去写一个资源时就有危险。不可变对象不能被重写所以是安全的。

本内容部分参考原址

猜你喜欢

转载自blog.csdn.net/qq_36952611/article/details/72722628