String不可变指内容不可修改,本质是“内容锁死”:final类、私有不可变底层数组、所有方法返回新对象;常量池共享、HashMap键稳定、多线程安全均依赖此特性。
String不可变,本质是“内容锁死”,不是“变量锁死”
很多人看到 str = str + "a" 就以为字符串被修改了,其实只是变量 str 指向了一个新对象;原来的字符串对象(比如 "Hello")在堆里纹丝不动,哪怕没人再引用它,也得等GC来收——这才是“不可变”的真实含义。
关键实现靠三重防护:final class String 阻止继承、private final char[] value(JDK8及之前)或 private final byte[] value(JDK9+)封住底层数组引用、所有 public 方法(如 substring()、replace())全部返回新对象,绝不碰原数组一个字节。
- 别信“
final数组就安全”这种说法:final int[] arr = {1}仍可改arr[0] = 2;String 安全靠的是封装 + 不暴露value+ 所有方法绕开原数组操作 - 反射强行修改
value是可能的(比如用Unsafe或Field.setAccessible(true)),但这属于破坏 JVM 合约,生产环境严禁,且 JDK9+ 的byte[]+ 编码压缩让反射更难生效
字符串常量池(String Pool)为什么必须依赖不可变性
当你写 String a = "abc"; String b = "abc";,JVM 让 a 和 b 共享同一个对象——这省内存、提速度。但如果 "abc" 可被修改,a.toUpperCase() 一调,b 看到的就变成 "ABC",逻辑直接崩坏。
常量池不是“功能开关”,而是不可变性的必然产物:只有内容永不改变,多个引用共享才敢放心。
立即学习“Java免费学习笔记(深入)”;
- 使用
new String("abc")会绕过常量池,在堆里新建对象,==比较必为false -
intern()是手动“拉回”常量池的补救手段,但代价是字符串首次入池时要查表,高频调用反而拖慢性能 - JDK7 把常量池从永久代移到堆内存后,
intern()行为更可控,但依然不建议在循环里滥用
HashMap 的 key 为什么非得用不可变对象
如果 String 可变,你把 "user" 当 key 存进 HashMap,之后又把它改成 "USER",它的 hashCode() 就变了——但 HashMap 还按旧 hash 值找桶,结果 map.get("user") 返回 null,数据“消失”了。