String不可变性#
一句话答案#
String 用 final 类 + private final 数组(JDK 8 是 char[],JDK 9 起是 byte[] + coder)实现不可变,好处:线程安全、哈希值缓存、字符串池复用、安全性。
核心要点
实现: final类 + private final char[](JDK 9 起为 byte[] + coder)+ 不提供修改方法
好处: 1.线程安全 2.hashCode缓存(HashMap的key) 3.字符串常量池复用 4.安全性(不可被篡改)
面试回答(2分钟版)
String 的不可变性通过三道防线实现:第一,String 类本身是 final 的,不能被继承,防止子类破坏不可变性;第二,底层存储字符的 char 数组声明为 private final,final 保证引用不能指向新数组,private 防止外部直接访问;第三,String 类不提供任何修改内部数组的方法,所有看起来像修改的操作如 concat、replace 都是返回新的 String 对象。不可变性带来四个重要好处:线程天然安全,多线程共享不需要同步;hashCode 可以在首次计算后缓存起来,作为 HashMap 的 key 时效率极高;支持字符串常量池机制,相同内容的字符串可以复用同一个对象节省内存;用于类加载器路径、网络连接参数等安全敏感场景时不怕被篡改。JDK 7 之后字符串常量池从方法区移到了堆中。理论上 JDK 8 可以通过反射修改 String 内部数组;JDK 9 引入模块系统后先是警告,JDK 16/17 起默认强封装,不加 —add-opens 就改不了。另外 JDK 9 起(JEP 254 Compact Strings)底层数组从 char[] 改成了 byte[] 加 coder 标记,但不可变的三道防线不变。
追问与易错
追问方向:
- “String 不可变是怎么实现的?”→ 三道防线:String 类声明为 final 不可继承、底层 char[] 为 private final 引用不可变且外部不可访问、不提供任何修改内部数组的方法
- “可以通过反射修改 String 吗?”→ JDK8 可以通过反射 setAccessible(true) 修改 value 数组内容;JDK 9–15 引入模块系统后默认
--illegal-access=permit,仍能改、只打警告;JDK 16(JEP 396)默认拒绝,JDK 17(JEP 403)强封装、该参数失效,只能显式--add-opens java.base/java.lang=ALL-UNNAMED才能改 - “字符串常量池在哪里?”→ JDK7 之前在方法区(永久代),JDK7 开始移到堆中,便于 GC 回收不再使用的常量字符串,避免永久代 OOM
易错点:
- ❌ “final char[] 就保证了不可变”——final 只保证引用不变,还需要不暴露修改方法
- ❌ 混淆 String.intern() 的行为——JDK7+ 不再复制字符串到常量池,而是存引用