最近经常看到如何理解String类型不可变性的文章,总说new的对象指向堆,同时在常量池也创建对象,而不new的对象直接指向常量池,但是考虑到String内部有一个byte[] value数组,所以一时间有点迷惑到底怎么引用的,所以查阅了一些资料之后记录下来,以便查阅
首先可以看到下面这样一张图,在上半部分中是new出来的对象,首先会判断常量池中是否有这个对象,有则创建,再在堆空间中创建一个对象,在下半部分是不new的对象,变量直接指向了常量池里面的对象。那如果加入上面据说的byte[] value数组,这之间的引用又是怎样的呢?
其实在上面的图中并没有画出value这个数组,里面所有的myString都是指的一个字符串对象。加入value后的完整过程如下:
1.new出来的对象: 首先判断常量池中是否存在需要的字符串对象,如果没有则在常量池中开辟一个空间存在该对象,记为o1,同时,在常量池中的这个字符串对象的value数组指向了另外一个在常量池新开辟的空间,这个空间才是真正存放数据的地方,记为b1。之后再在堆空间中创建一个字符串对象,记为o2,该对象的value数组指向的也是常量池中的b1
2.不是new出来的对象: 在创建时判断在常量池中是否存在需要的对象,如果不存在就开辟空间,创建字符串对象,同时为value数组也开辟空间,存在数据。在栈中的引用就指向了该常量池中的对象。
上面的过程的完整流程图如下:
下面说说对于String类型不可变性的理解:
由于在String中声明的value数组是final修饰的,也就意味着该数组的引用是不可改变的,同时value也被private修饰,无法从外部修改value的内容(反射除外),所以当我们想改变字符串的内容的时候,会在常量池中另外开辟一个空间,创建一个新的字符串对象,这个对象的value数组又指向一个新的空间,用来存放数据。
为什么String和内部的value都被final修饰:
1、为了实现字符串常量池:被final修饰才使字符串是不可变的(private修饰value也起重要作用),而不可变性是实现字符串常量池的基础(如果是可变的就没有办法让所有相同字符串都指向同一个对象,也就没有办法节省空间),让字符串变量尽可能直接指向常量池中的对象,达到节省heap空间的目的
2、为了线程安全:
由于字符串是不可变的,所以只能对其进行读取,不存在修改也就不存在冲突,所以是多线程安全的。当其中某些线程试图修改字符串时只会创建新的对象
3、实现HashCode的不变性
由于其不可变性,字符串对象在创建时HashCode就被缓存下来,之后在使用的时候就不用重复计算。所以String类型非常适合作为HashMap中的键,就是因为对Map的键要做多次Hash计算,如果该键的HashCode不会变就不用重复计算了
总结: 字符串的上面三个特性都是由其不可变性引发的,而被final和private修饰是其具有不可变性的基本要求