String不可变性由final类、private final byte[] value与coder字段、防御性拷贝及无修改API四重机制共同保障,支撑常量池复用、hashCode缓存、线程安全等核心能力。

String 不可变性不是一句“final 类 + final 数组”就能糊弄过去的。面试官真问“深入底层机制”,就是在等你把 JVM 内存模型、类加载行为、对象构造细节和设计权衡全串起来讲清楚。
源码里三道硬锁,缺一不可
以 JDK 17 为例,核心字段是:
- final class String:禁止继承,杜绝子类重写 toString()、hashCode() 等方法破坏一致性
- private final byte[] value:引用不可重赋值,且外部连数组本体都拿不到(private 封死访问路径)
- private final byte coder:编码标识与 value 绑定,改了 value 不同步 coder 就会乱码,所以连 coder 都不暴露
注意:构造方法里还有 Arrays.copyOf() 做防御性拷贝——哪怕你传入一个外部可变的 byte[],String 也会复制一份,彻底切断外部篡改通道。
常量池复用依赖不可变,否则直接崩掉
字符串字面量如 "abc" 在类加载时就进常量池。如果 String 可变:
- String s1 = "abc"; String s2 = "abc"; → s1 和 s2 指向同一个对象
- 若 s1.replace('a', 'x') 真改了原对象,s2 的值瞬间变成 "xbc"
- 整个共享内存模型失效,JVM 根本不敢做对象复用
不可变是常量池能安全存在的前提,不是结果。
hashCode 缓存和线程安全是自然结果,不是设计目标
String 的 hash 字段是 lazy 计算、只算一次:
- 第一次调用 hashCode() 时计算并缓存到 int hash 字段
- 因为 value 永远不会变,hash 值永远有效
- 所以 String 能高效当 HashMap 的 key —— 不用每次 get/put 都重新算 hash
- 多线程读同一个 String(比如配置项、SQL 模板),完全不用 synchronized 或 volatile
这些不是“为了缓存而不可变”,而是不可变之后,这些优化水到渠成。
反射能破?但那已经不属于 Java 正常语义了
用反射修改 value 数组,在 JDK 8 确实能改出 "Hello",但这只是绕过访问控制的 hack:
- JVM 不保证这种操作的行为一致性(JDK 9+ 的 coder 校验会让它直接报错)
- 破坏了类加载器信任链,可能触发 SecurityManager 拒绝
- 面试中说“不可变”指的就是标准 API 下的行为,不是钻反射漏洞
真正要答出深度,得点明:不可变是 Java 对字符串这个基础类型的契约承诺——它让字符串从“一段数据”升格为“可信的值对象”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











