java中string的不可变性是一套严谨的设计契约:类被final修饰、内部字符数组(jdk 9+为byte[])用final声明,所有“修改”方法(如concat、substring、replace)均返回新对象;它不阻止s = s + "x"这类赋值,但每次操作都创建新对象,原字符串保持不变;该特性支撑字符串常量池复用、线程安全、哈希码缓存及类加载器可靠性,但也带来频繁拼接时的gc压力,需用stringbuilder/stringbuffer优化。

Java中String的不可变性不是语法限制,而是一套严谨的设计契约:类被final修饰、内部字符数组(JDK 9+为byte[])用final声明、所有“修改”方法(如concat、substring、replace)都返回新对象。它不阻止你写s = s + "x",但每次赋值背后都是新对象诞生——原字符串毫发无损。
字符串常量池与内存复用
不可变性是字符串常量池(String Pool)存在的前提。JVM只在池中存一份字面量,多个变量可安全共享同一底层数据:
-
String a = "hello"; String b = "hello";→a == b为true,因为它们指向池中同一个对象 -
String c = new String("hello");→ 即使内容相同,c在堆中新建对象,a == c为false - 调用
c.intern()会将c对应的内容注册进常量池(若尚不存在),之后再取同值字面量即可复用
线程安全与哈希缓存优势
没有同步开销就能在多线程间自由传递,这是不可变性最直接的红利:
- 作为
HashMap或ConcurrentHashMap的键时,hashCode()在首次调用后被缓存(String内部有hash字段),后续不再重复计算,查找效率稳定 - 即使多个线程同时读取同一个
String实例,也绝不会出现因状态变更导致的竞态或不一致 - 类加载器使用
String表示类名、包名,不可变性防止核心路径被中途篡改
性能陷阱与应对策略
不可变带来安全和复用,但也让频繁拼接变成“对象工厂”:
- 循环中用
+=拼接10万次字符串,可能产生数十万个临时String对象,显著加重GC压力 - 应改用
StringBuilder(单线程)或StringBuffer(多线程)进行可变操作,它们内部维护可修改的字符数组 - 编译器对简单
+表达式(如"a" + "b" + "c")会在编译期优化为常量,但运行期拼接无法优化,必须人工干预
安全敏感场景的特殊考量
不可变性在安全领域是一把双刃剑:
- 密码、密钥等敏感信息若存为
String,一旦进入内存就无法清除(GC前长期驻留),存在被内存dump窃取风险 - 推荐改用
char[]存储,并在使用后立即手动清空:Arrays.fill(pwdArray, '\0') - 配置项、数据库URL、SQL模板等使用
String则非常合适——不可变确保传入后不会被下游意外覆盖或污染
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











