String的不可变性是逻辑保障而非绝对强制,反射可修改其底层数组,导致常量池污染、hashCode缓存失效、线程安全与安全假设被破坏等严重后果。

String 的不可变性在 Java 正常语义下是牢靠的,但它不是由 JVM 或硬件强制保障的“绝对不可变”,而是一种基于设计约定 + 语言机制 + API 约束共同构建的逻辑保障。反射可以绕过这套约束,强行修改底层字符数组(JDK8 是 char[],JDK9+ 是 byte[]),但这会直接冲击整个字符串机制的根基。
反射修改 String 的底层操作
核心步骤其实很直接:
- 通过
String.class.getDeclaredField("value")获取私有字段 - 调用
setAccessible(true)绕过访问控制 - 用
get()拿到内部数组引用(char[]或byte[]) - 直接修改数组元素,例如
array[0] = 'X'
这一过程不创建新对象,也不改变变量引用,原 String 实例的内容就被静默覆盖了。
最危险的后果:常量池污染
这是反射改 String 最具破坏性的表现。由于字符串字面量(如 "abc")默认进入运行时常量池,多个相同字面量的变量实际共享同一对象:
-
String a = "hello"; String b = "hello";→a == b为true - 若用反射修改
a的value数组,b的内容也会同步变成被篡改后的值 - 所有指向该常量池项的 String 变量,读取时都得到错误内容
这不再是单个对象的问题,而是全局性、隐蔽性强、难以排查的污染。
hashCode 缓存失效但不更新
String 内部缓存了 hash 字段(初始为 0),首次调用 hashCode() 后才计算并赋值。反射修改字符数组后:
- 字符内容变了,但缓存的
hash值不会自动重算 - 后续调用
hashCode()仍返回旧值(因为hash != 0,跳过重新计算) - 导致
equals()返回true,但hashCode()不一致 → 违反哈希契约,HashMap/HashSet 行为异常
破坏线程安全与安全假设
不可变性是很多基础保障的前提:
- 多线程共享配置字符串(如数据库 URL、SQL 模板)时,开发者默认它不会被其他线程意外修改;反射一旦介入,这个前提崩塌
- 安全框架依赖 String 存储路径、权限标识、token 等;内容被篡改可能绕过校验或触发非法操作
- 类加载器、安全管理器等底层组件也隐式信任 String 的稳定性
这些不是理论风险,而是在真实系统中可能引发崩溃、越权或数据错乱的实际隐患。

















