为什么 java 中的 string 是不可变的
大家好,今天咱们来聊聊一个Java面试里老生常谈,但确实非常核心的基础问题:为什么 Java 中的 String 是不可变的? 这个问题看似简单,却直接关联到Java语言的安全性、性能和内存管理机制。理解透彻了,面试的时候就能说得头头是道。
对了,在开始深入之前,分享个好东西给大家: 我整理了一份 2025年Java面试宝典,涵盖了Java核心、并发、JVM、数据库、框架等高频考点,需要的朋友可以下载备用: 链接: https://pan.baidu.com/s/1RUVf75gmDVsg8MQp4yRChg?pwd=9b3g 提取码: 9b3g

理解“不可变”的含义
首先,明确一点,String 不可变 到底啥意思?简单说就是:一个 String 对象一旦被创建出来,它里面包含的那个字符序列(也就是字符串的值)就 固定下来 了,再也不能被修改了。
你可能会想:“不对啊,我写 str = str + "World"; 这不是改变了吗?” 注意了!这其实是个经典的误解。这个操作并没有改变原来那个 "Hello" 对象本身。它实际上是:
- 创建了一个新的字符串对象
"World"。 - 然后创建了一个全新的字符串对象
"HelloWorld"。 - 最后,让变量
str指向了这个新创建的"HelloWorld"对象。
原来的 "Hello" 对象还在内存里躺着呢,只是 str 不再引用它了(如果没有其他引用,它最终会被垃圾回收)。这就是 String 不可变 的本质:对象的内容不变,变的只是变量的引用。

Java 设计者为什么要让 String 不可变?
弄清楚了什么是“不可变”,那接下来就是关键了:为什么 Java 要这样设计?这背后有非常充分的理由,主要围绕着安全、效率和设计三大方面:
1. 安全性 (Security)
- 类加载与敏感信息: Java 加载类时,需要传递类名(字符串)。想象一下,如果字符串是可变的,恶意代码在你不知情的情况下修改了这个类名字符串的内容,就有可能加载到错误的、甚至有害的类,这会造成严重的安全漏洞。String 的不可变性 保证了像类名、文件路径、网络连接参数等关键字符串在传递和使用过程中不会被篡改,筑牢了安全防线。
- 参数传递: 字符串经常作为方法的参数(比如数据库连接URL、用户名密码等)。如果它们可变,方法内部或者通过其他引用修改了这些字符串,就可能引发意料之外的副作用,破坏程序的逻辑甚至导致安全问题。不可变性消除了这种潜在风险。
- 哈希码的稳定性:
String广泛用作HashMap、HashSet等集合的键 (Key)。这些集合依赖键的hashCode()来快速定位存储位置。如果String可变,当它的内容被修改后,它的hashCode()也会改变!这就导致集合再也无法通过原来的哈希值找到这个键值对了(数据会“丢失”在错误的桶里),破坏了集合的可靠性和正确性。String 的不可变性 保证了它的hashCode()在对象生命周期内恒定不变,这是集合类高效、正确工作的基石。
2. 性能优化 (Performance)
- 字符串常量池 (String Pool): 这是 Java 性能优化的一大法宝!因为
String不可变,所以 JVM 可以在内存中开辟一块特殊区域——字符串常量池。当你用双引号 ("...") 创建一个字符串字面量时,JVM 会先去池子里找有没有内容完全相同的字符串。- 如果找到了,就直接返回池中那个现有对象的引用,避免重复创建新对象。
- 如果没找到,才在池子里创建一个新对象并返回引用。
- 这种 重用机制 极大地节省了内存,尤其是程序中充斥着大量重复字符串(比如常见的单词、标识符)时效果显著。同时,减少了对象创建和垃圾回收的压力。这一切都建立在 String 不可变 的基础之上。如果字符串可变,这种池化共享就完全行不通了,因为一个地方修改会影响到所有共享该对象的地方。
- 哈希码缓存: 前面提到
hashCode()在集合中的重要性。因为String不可变,Java 可以在String对象创建时,就计算好它的哈希码并缓存起来(String类内部有个私有字段hash存储这个值)。后续无论调用多少次hashCode(),都直接返回这个缓存值,避免了重复计算的性能开销。这对于频繁作为键的String来说至关重要。如果字符串可变,缓存哈希码就毫无意义且可能导致错误。 - 其他优化: 不可变性简化了编译器和 JVM 的其他优化,比如在某些情况下可以安全地进行代码合并和推断。
3. 线程安全 (Thread Safety)
- 因为
String不可变,它的状态(即字符序列)在创建后就不能再改变。这意味着多个线程可以同时、安全地读取同一个String对象,而不需要任何额外的同步锁。它是天然线程安全的。 - 在多线程环境下传递和共享字符串变得非常简单和可靠,不用担心并发修改导致的数据不一致问题。想象一下网络请求处理、并发集合操作等场景,
String的不可变性提供了极大的便利和安全性。
4. 设计与可靠性 (Design & Reliability)
- 简化设计与使用: 不可变对象的状态单一且固定,这简化了类的设计,也使得使用起来更可预测、更不容易出错。开发者不需要担心对象被谁、在何时修改。
- 作为父类:
String类被声明为final,防止被继承。这进一步强化了它的不可变特性,避免了子类通过继承破坏其不可变性。这个final声明和 不可变 的设计是相辅相成的。 - 其他类的基石: Java 中许多重要的类都依赖于
String的不可变性,比如ClassLoader、SecurityManager、Properties(使用String做键) 等等。String的可靠性和安全性是这些类稳定工作的前提。
不可变带来的“不便”与解决方案
当然,String 不可变 也不是没有“代价”。最直接的就是当我们确实需要频繁修改字符串内容时(比如在一个循环中拼接大量字符串),不断创建新的 String 对象会产生大量的中间垃圾对象,影响性能。
为此,Java 提供了两个专门用于可变字符串操作的类:
StringBuilder: 非线程安全,但性能最高。适用于单线程环境下的字符串拼接、修改。StringBuffer: 线程安全(方法用synchronized修饰),性能略低于StringBuilder。适用于需要在多线程环境下修改字符串内容的场景。
它们的原理都是在内部维护一个可变的字符数组 (char[]),进行修改操作(append, insert, delete 等)时,直接在这个数组上操作,避免创建大量新对象。在需要最终结果时,调用它们的 toString() 方法生成一个新的、不可变的 String 对象。
所以,最佳实践是:在不需要修改字符串内容时,使用 String;在需要频繁修改字符串内容时,使用 StringBuilder (单线程) 或 StringBuffer (多线程)。

总结与面试建议
为什么 Java 中的 String 是不可变的?核心答案围绕安全、性能(尤其是字符串常量池和哈希缓存)、线程安全这三大支柱。理解这些设计背后的深意,才能让你在面试中脱颖而出。
记住几个关键点:
- 不可变 意味着对象内容创建后不可更改,修改操作实际是创建新对象。
- 安全: 防止关键字符串(如类名、参数)被篡改。
- 性能: 启用字符串常量池实现重用,节省内存;缓存哈希码提升集合操作效率。
- 线程安全: 天然线程安全,多线程读取无需同步。
- 需要频繁修改用
StringBuilder/StringBuffer。
如果大家在准备面试的过程中需要购买面试鸭会员,可以通过 面试鸭返利网 (mianshiyafanli.com) 找到我,还能享受返利25元的优惠哦!
熟练掌握 String 不可变 的原理和影响,是Java程序员基本功的体现。希望这篇文章能帮你理清思路,面试时自信作答!别忘了下载开头的面试宝典持续充电!


