String的不可变指的是

  • String内部是使用一个被final修饰char数组value存储字符串的值
  • 数组value的值在对象构造的时候就已经进行了赋值
  • String不提供方法对数组value中的值进行修改
  • String中需要对value进行修改的方法(例如replace)则是直接返回一个新的String对象
    所以String是不可变的。

String是如何实现其对象不可变?

我们来看一下String类的两个主要成员变量,其中value指向的是一个字符串数组,字符串中的字符就是用这个value变量存储起来的,并且用final修饰,也就是说value一旦赋予初始值之后,value指向的地址就不能再改变了。虽然value指向的数组是可以改变的,但是String也没有提供相应的方法让我们去修改value指向的数组的元素。然而在StringBuilder中是提供了响应的方法让我们去修改value指向的数组的元素,这也是StringBuilder的字符串序列可变的原因。



/** The value is used for character storage. */
  private final char value[];

  /** Cache the hash code for the string */
  private int hash; // Default to 0



String使用中有一些看似可以改变String对象的方法,但实际上它们已经是指向了一个新建的对象。

程序例子:




stringRedisTemplate怎么修改数据 改变string的值_string类为什么是final的


运行结果:


stringRedisTemplate怎么修改数据 改变string的值_string类为什么是final的_02


程序分析:


stringRedisTemplate怎么修改数据 改变string的值_string replace_03


str1+=str2实际上是执行了str1=(new StringBuilder()).append(str2).toString();前后实际额外产生了一个StringBuilder与一个helloworld的字符串常量。str1执行+=前后内存的示意图如下所示:


stringRedisTemplate怎么修改数据 改变string的值_字符串_04


上面使用了String类的concat与replace方法,执行这两个操作不会对原来的对象产生影响,他们会返回一个全新的对象。我们可以来看一下这两个方法的源码。

concat方法源码:


stringRedisTemplate怎么修改数据 改变string的值_string replace_05


replace方法源码:


stringRedisTemplate怎么修改数据 改变string的值_string类为什么是final的_06


为什么String设计成不可变的

字符串常量池的需要,提升效率和减少内存分配
安全性考虑,防止被意外修改(HashSet中存的值如果是可变的String,则破坏了唯一性;不可被写所以线程安全;)
作为HashMap、HashTable等hash型数据key的必要。因为不可变的设计,jvm底层很容易在缓存String对象的时候缓存其hashcode,这样在执行效率上会大大提升。

String对象真的不可变吗?

虽然value是final修饰的,只是说明value不能再重新指向其他的引用。但是value指向的数组可以改变,一般情况下我们是没有办法访问到这个value指向的数组的元素。但是,通过反射可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。


stringRedisTemplate怎么修改数据 改变string的值_数组_07


运行结果:


stringRedisTemplate怎么修改数据 改变string的值_string replace_08


修改前的str:Hello World 修改前的str的内存地址1922154895

修改后的str:Hel?o World 修改前的str的内存地址1922154895

可以看到str的字符串序列已经被改变了,但是str的内存地址还是没有改变。

----------------------------------------------------------------------------------------------