java中string不可变的根本原因是其内部字符数组被final修饰且无修改方法:jdk8前为private final char[] value,jdk9起改为private final byte[] value配合coder字段;所有操作如substring、tolowercase均返回新对象而非修改原对象。

Java 中的 String 不可变,根本原因在于其内部字符数组被 final 修饰且不对外暴露修改入口,同时所有可能改变内容的方法(如 substring、toLowerCase、concat)都返回新对象,而非修改原对象。
不可变的核心实现:final char[] + 无修改方法
在 JDK 8 及之前,String 的底层是 private final char value[];JDK 9 起改为 private final byte[] value(配合 coder 字段支持紧凑字符串)。关键点有二:
- value 数组声明为 final:意味着引用不可变——一旦指向某块内存,就不能再指向别处
- value 数组本身不可被外部修改:没有提供 setCharAt、clear、resize 等任何修改数组内容的 public 方法
- 所有看似“改变”字符串的操作(比如 "abc".toUpperCase()),实际都 new 了一个新 String 对象,原对象毫发无损
为什么设计成不可变?不只是为了安全
不可变性带来多重收益,不是拍脑袋决定的:
- 字符串常量池(String Pool)才能高效复用:如果 String 可变,"abc" 被多个变量引用,一个改了内容,其他全乱套——常量池直接失效
- 天然线程安全:没有状态可变,就不需要同步,多线程下直接共享无压力
- 可作为 HashMap / HashSet 的 key:hashCode 在构造时缓存(JDK 7+),不会因内容变化导致哈希错位、丢失数据
- 提升安全性:类名、文件路径、网络地址等关键字符串传入敏感 API 时,无法被恶意篡改
几个典型方法的源码佐证(以 JDK 8 为例)
看几个常用方法如何“假装改变实则新建”:
立即学习“Java免费学习笔记(深入)”;
- concat(String str):new String(...) 构造新对象,拷贝原 value 和参数 value 的全部字符
- substring(int beginIndex):JDK 7u6 之前会共享原 value 数组(有内存泄漏风险),之后改为显式复制子串,确保原对象不受影响
- replace(char oldChar, char newChar):遍历原 value,发现匹配才新建 char[],最后 new String(value, 0, value.length)
注意:“不可变”不等于“不能反射破坏”
严格来说,通过反射可以绕过访问控制修改 value 数组(例如 setAccessible(true) 后修改 final 字段),但这属于 hack 行为,破坏 JVM 合约,会导致:
- 常量池逻辑崩溃(比如 "abc".intern() 返回的对象内容被改)
- hashCode 缓存与实际内容不一致
- JVM 可能抛出 IllegalAccessException 或在某些版本直接拒绝操作
所以,不可变是语言契约和 API 设计层面的保证,不是绝对的物理防护。
基本上就这些。String 的不可变性是 Java 基础中少有的“小设计、大影响”的典型——看着简单,背后全是权衡和深意。









