String在Java中真的是不可变吗

张开发
2026/4/14 9:14:14 15 分钟阅读

分享文章

String在Java中真的是不可变吗
先说结论String毫无疑问是不可变的不管是在JDK8之前还是在JDK8之后String的设计就是往不可变设计的往往有人会错误的认为String可变的原因我想大多数是没弄懂对象在内存的存储关系把变量和对象本身弄混淆了比如下面这段代码public static void main(String[] args) { String s1 hello; s1 world; }有些人可能就认为s1对象不是从hello变为world了吗明明对象是可变的但是我们探讨的String对象是否可变实际上是String对象本身而这里的s1仅仅是指向它的一个引用引用变量但是String对象本身还是不变hello还是那个hello存储在字符串常量池【变量可以重新指向别的对象但对象本身内容不会变这就是不可变】接下来从String的代码中探究为何对象本身就是不可变的设计public final class String implements java.io.Serializable, ComparableString, CharSequence { private final char value[]; }首先是String类的修饰语义fianl修饰意味对象本身不可继承不会被子类继承之后修改其次是String对象的内容载体内部是由private final修饰char数组private修饰意味着外部压根无法访问到final让这个引用一旦在构造方法赋值就不能指向别的数组并且在JDK9之后对String的内容载体做了一层优化由char存储内容变为由byte存储Stable private final byte[] value;这样设计空间缩减一半[目前可以浅显的认为虽然特定情况下还是会转为char存储]并且Stable注解的作用就是告诉JVM这个数组的内容在初始化之后不会再变了JVM可以放心地对它做常量折叠等优化读到这里我们可能会认为String内部的内容载体final修饰的是引用啊我们把这个引用指向别的对象内容不就变了吗但是问题是final之前还有一个private修饰这就意味着外部操作压根拿不到这个引用谈何变化呢并且最后一点String中所提供出来能被外部访问的操作方法比如concat()、replace()、substring()、toUpperCase()​实际上都是创建并返回一个新的对象不是直接对原对象做操作总结String对象之所以不可变的原因有三层防护第一层String本身由final修饰不可被继承也就不会被子类重写导致对象变化第二层String内部数据载体由private final修饰不能被外部访问和替换第三层String所提供的所有方法均是返回新对象而非对源对象做修改后返回这三层保护从根本上保证String不可被修改还有人会说我要是直接通过反射呢在JDK8之前确实可以通过反射获取到String内部的引用直接修改char数组元素String内容就变了但是本质上我们讨论String不可变是基于它的设计理念反射属于主动破坏设计契约。并且从JDK9开始Java引入模块系统String的内部数据载体属于模块的内部实现默认不对外开放反射访问如果尝试用反射去修改会抛异常这也就意味着从JDK9开始String的不可变连反射这条路都堵上了String的不可变性在现代Java版本是全方位保证的无需程序员自觉维护从String不可变类的设计范式去看别的类String也并不是Java中唯一不可变的类这种设计范式在jdk中很常见如下表​基本上所有的不可变类都是fianl修饰内部数据载体字段由private final修饰并且即便提供出修改操作也都是返回新对象我们在自己设计一个不可变类也可套用这套设计范式文章参考原文https://www.zhihu.com/question/596214547/answer/2018645770841142787

更多文章