Java中String不可变性由final private字段和JVM约束共同保障:value(byte[])、coder(byte)均不可变,hash仅缓存;所有“修改”操作返回新实例;常量池复用、线程安全、内存优化(LATIN1/UTF16自适应)均源于此。

Java 中 String 的不可变性,本质是由其内存存储结构和字段修饰共同决定的——不是靠“约定”,而是靠 JVM 层面的强制约束。
底层字段被 final + private 严格保护
从 JDK 9 开始,String 的核心数据字段是:
- private final byte[] value:真正存字符数据的字节数组,final 保证引用不可重指向,private 阻止外部访问或修改数组元素;
- private final byte coder:标识编码方式(LATIN1 或 UTF16),同样不可更改;
- private int hash:哈希值缓存,只在首次调用 hashCode() 时计算并赋值,后续复用,不参与字符串内容表达,但依赖内容不变才有效。
这三个字段加在一起,让 String 对象一旦创建,其内容序列、长度、编码方式、哈希结果全部锁定。
对象创建位置影响引用关系,但不破坏不可变性
字符串字面量(如 "abc")直接进入字符串常量池(JDK 7+ 在堆中),而 new String("abc") 会在堆中新建对象。但无论在哪创建:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 新对象内部的 value 数组仍为 final,无法被修改;
- 所有看似“修改”的方法(如 substring、replace、concat)都返回全新 String 实例,原对象毫发无损;
- 变量名(如 String s = "abc")只是栈中一个地址引用,重新赋值(s = "def")只是让这个引用指向另一个地址,不影响原对象。
不可变性带来的内存行为特征
这种设计直接反映在运行时表现上:
- 常量池复用:String a = "hello"; String b = "hello"; → a == b 为 true,因为共用同一份 value 数据;
- 拼接不改变原对象:String s = "a"; s.concat("b"); 后 s 仍是 "a",未发生任何内部变更;
- 线程间安全共享:public static final String CONFIG = "timeout=30"; 可被任意线程读取,无需同步,因为没人能改它。
为什么不用 char[] 而改用 byte[]?
这不是为了“可变”,恰恰是为了更精细地支撑不可变性下的内存效率:
- JDK 8 以前用 char[],每个字符固定占 2 字节,英文字符严重浪费空间;
- JDK 9 引入 byte[] + coder,自动按内容选择 LATIN1(1 字节/字符)或 UTF16(2 字节/字符),相同语义的字符串在内存中更紧凑;
- 用户完全无感:length() 仍返回字符数,charAt(i) 行为不变,但底层节省的内存真实存在。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










