string不可变性由final类、private final byte[] value字段及无状态api三重机制共同保障,确保常量池安全复用、线程安全与hashcode缓存等核心特性。

String 的不可变性不是靠单个关键字撑起来的,而是由类声明、字段设计、API 行为三者共同锁定的结果;字符串常量池能安全复用的前提,正是这种不可变性被严格保障。
从 JDK 源码看 String 类的三重不可变防护
以 JDK 17(主流 LTS 版本)源码为例:
- 类被 final 修饰:public final class String —— 阻止继承,杜绝子类通过重写方法绕过约束;
- 底层存储是 private final byte[] value:JDK 9+ 用 byte[] 替代 char[] 节省内存,private 封装防止外部访问,final 保证数组引用不可重指向;
- 所有“修改”操作都返回新对象:substring()、replace()、concat()、toUpperCase() 等方法内部都会 new String(...) 或调用构造器复制内容,原对象始终 untouched。
注意:final byte[] 只锁住数组引用,不锁数组元素——但 String 没提供任何 public 方法去改 value[i],且所有构造和操作逻辑都规避了原地修改,这才是真正落地的不可变。
字符串常量池如何依赖不可变性工作
常量池本质是 JVM 堆中一块共享缓存区(JDK 7+ 移至堆内存),它的复用机制必须建立在“内容不变”的前提上:
- 当执行 String s = "hello"; JVM 查常量池,若已有 "hello",直接返回其引用;
- 如果 String 可变,s.toUpperCase() 就可能把池中 "hello" 改成 "HELLO",导致其他同样引用它的变量(如 config.url = "hello")意外失效;
- 正因为不可变,JVM 才敢让 dozens 个变量共用同一个 "abc" 实例——改一个,不影响任何一个。
字面量 vs new:验证常量池与不可变性的联动
写一段对比代码就能看到效果:
- String a = "test"; String b = "test"; → a == b 为 true:两者都从常量池取,指向同一不可变对象;
- String c = new String("test"); String d = new String("test"); → c == d 为 false:每次 new 都在堆新建对象,即使内容相同也不进池;
- c.intern() == a 为 true:intern() 主动把堆中字符串注册进常量池,后续再查就命中。
这个行为差异背后,是 JVM 对“不可变对象才可全局共享”这一原则的严格执行。
不可变性被破坏的边界情况(仅作认知补充)
严格来说,Java 层面的不可变是“语义不可变”,不是物理绝对封锁:
- 通过反射可访问 private final value 字段并修改数组元素(如把 "abc" 改成 "axc"),但这属于绕过语言契约的非常规操作;
- Unsafe 类也能直接操作内存地址,但 JDK 9+ 默认禁用,且生产环境严禁使用;
- 这些手段不影响 String 的设计意图和绝大多数场景下的行为可靠性。











