Java String不可变是通过final类、private final底层数组(JDK9+为byte[])、所有方法返回新对象三重防护实现的;支撑常量池复用、hashCode缓存、线程安全及类加载信任等核心机制。

Java String不可变,不是“不能重新赋值”,而是对象内容一旦创建就无法更改。变量可以指向新对象,但原String对象的字符序列永远固定——这是设计选择,不是语法限制。
不可变性怎么实现的?
靠三层防护:
- 类被final修饰:String类不能被继承,避免子类篡改行为
-
存储数组被private final修饰:JDK 8是
private final char[] value,JDK 9+改为private final byte[] value,引用不可重定向,外部无法访问 - 所有“修改”方法都返回新对象:substring、replace、trim、concat等内部不改动原数组,而是拷贝或截取后构造新String
为什么这么设计?关键好处在哪?
不可变不是为了炫技,而是支撑重要机制:
-
字符串常量池复用:相同字面量如
"abc"只存一份,多个变量共享,省内存 -
哈希值可缓存:hashCode()计算一次后存入
hash字段,作为HashMap键时性能稳定 - 天然线程安全:多个线程读同一个String无需同步,不会因并发修改出错
- 类加载与安全校验可靠:类名、包名、资源路径等依赖String,不可变保证逻辑不被意外篡改
开发中必须注意的典型陷阱
理解原理才能避开坑:
立即学习“Java免费学习笔记(深入)”;
- 拼接大量字符串别用+或concat:每次都会新建对象,O(n²)内存开销;循环内优先用StringBuilder
-
new String("xxx")多创建一个对象:除非明确需要堆中独立实例(如规避常量池污染),否则直接写
"xxx" -
==比较的是地址,不是内容:判断相等一律用
.equals(),尤其在Map key、用户输入校验等场景 -
反射能破坏不可变性,但不该用:通过
setAccessible(true)修改value数组属于危险操作,破坏JVM假设,仅用于教学演示
什么情况下该用可变替代方案?
String适合表示“值”,不适合高频构建:
- 单线程拼接/修改 → StringBuilder:非线程安全,但速度快,推荐日常使用
- 多线程拼接/修改 → StringBuffer:方法加了synchronized,安全但稍慢,实际项目中较少见
- 需要部分修改原内容 → 不要用String:比如日志模板填充、SQL语句组装,从一开始选StringBuilder更合理


















