Java String不可变性由final类、private final字段和无状态变更方法三重机制强制保障:类不可继承、引用不可重定向、所有操作返回新对象,支撑常量池复用、线程安全与hashCode缓存。

Java 中 String 的不可变性不是靠文档承诺,而是由源码中几处关键设计共同锁死的。它不依赖程序员自觉,而是通过语言机制强制保障。
final 类 + final 字段:从继承和引用两个层面封死修改路径
String 类被 final 修饰,无法被继承,排除了子类覆盖方法、绕过约束的可能性。同时,其核心数据字段(JDK 8 是 private final char[] value,JDK 9+ 是 private final byte[] value)也被 final 修饰——这意味着一旦对象初始化完成,value 的内存地址就再也无法指向别处。
- 即使你用反射强行修改 value 数组里的某个字节,也只是破坏了单个对象的数据完整性,并未改变“String 对象内容可被公开修改”的契约;JVM 和标准 API 仍视其为不可变
- 没有 public 方法能让你拿到 value 的引用并调用
array[i] = x,所有看似“修改”的方法(如substring、toLowerCase、replace)都明确返回新 String 实例
无状态变更方法 + 私有封装:对外彻底屏蔽内部改动能力
String 所有 public 方法都不改变自身字段值。以 replace(char oldChar, char newChar) 为例,它的实现逻辑是:
- 遍历当前 value 数组,找出匹配字符
- 新建一个 byte[] 或 char[],把替换后的内容拷贝进去
- 用这个新数组构造并返回一个全新的 String 对象
- 原对象的 value 字段全程未被写入,hash 字段也未被重算(除非你显式调用
hashCode())
哈希缓存与编码标志:不可变性支撑的性能优化细节
源码里还有两个容易被忽略但高度依赖不可变性的字段:hash 和 coder。
-
hash是 int 类型,初始为 0;首次调用hashCode()时计算并赋值,之后直接返回缓存值——这只有在内容永不变化的前提下才安全 -
coder是 byte 类型,标识当前字符串使用 LATIN1(0)还是 UTF16(1)编码;它在构造时确定,后续绝不更新——如果字符串可变,编码方式可能随内容动态变化,这个字段就会失效
常量池 intern 机制:不可变性是共享前提
当你写 String s = "abc",JVM 会检查字符串常量池是否已有相同内容的对象。如果有,就复用;否则新建并放入池中。这个机制成立的前提是:一旦放入池中,该对象的内容绝不能被任何代码改写。
- 如果 String 可变,s1 = "abc" 和 s2 = "abc" 共享同一对象后,s2.replace('a', 'x') 就会让 s1 变成 "xbc",引发不可预知的错误
- 正是不可变性让 JVM 敢于做这种对象复用,节省大量堆内存
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











