news 2026/6/9 17:27:29

Java 中的 String 类为何被设计成不可变(Immutable)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 中的 String 类为何被设计成不可变(Immutable)

一、先搞懂:什么是 String 的不可变?

String 的不可变指的是:一旦一个 String 对象被创建,它内部的字符序列(底层是char[] value数组,Java 9 后改为byte[])就无法被修改。看似修改 String 的操作(如拼接、替换),其实都是创建了一个全新的 String 对象,原对象不会有任何变化。

简单代码示例验证不可变:
public class StringImmutableDemo { public static void main(String[] args) { String s1 = "abc"; String s2 = s1; // s2和s1指向常量池中的同一个"abc"对象 // 看似修改s1,实际是创建新对象 s1 = s1 + "d"; System.out.println(s1); // 输出 "abcd"(新对象) System.out.println(s2); // 输出 "abc"(原对象未变) System.out.println(s1 == s2); // false,指向不同对象 } }

二、String 设计为不可变的核心原因

1. 安全性(最核心原因)

String 是 Java 中最常用的类,大量用于存储敏感信息(如用户名、密码、数据库连接串、网络请求参数等),不可变设计能从根本上避免这些数据被篡改:

  • 防止参数篡改:比如方法传参时,若 String 可变,方法内部修改参数会导致外部原变量被篡改,引发难以排查的 bug;
  • 类加载安全:JVM 类加载时会通过类名(String 类型)定位类文件,若 String 可变,可能被恶意修改类名,导致加载错误的类,引发安全漏洞;
  • 哈希表安全:String 常作为 HashMap、HashSet 的 Key,若可变,修改后哈希值会变化,导致 Key 对应的 Value 无法被正确查找,甚至引发哈希表混乱。
2. 支持字符串常量池(提升性能、节省内存)

Java 为了优化内存,设计了字符串常量池(String Pool)(位于堆内存的元空间):相同的字符串字面量只会在常量池中创建一次,所有引用都指向这个对象。

  • 不可变是常量池的前提:如果 String 可变,那么修改常量池中的一个 String 对象,会导致所有引用它的变量都被篡改,这显然不符合预期;
  • 性能提升:常量池复用字符串对象,避免了频繁创建和销毁对象的开销,大幅提升内存利用率。
代码示例:常量池复用
public class StringPoolDemo { public static void main(String[] args) { // 两个字面量都指向常量池中的同一个"hello"对象 String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); // true,引用同一个对象 // new String会创建新对象,不复用常量池(但内部value仍指向常量池) String s3 = new String("hello"); System.out.println(s1 == s3); // false } }
3. 天生的线程安全

不可变对象的状态在创建后就固定不变,多线程并发访问时,不需要加锁(同步)就能保证数据一致性,避免了线程安全问题。

  • 比如多个线程同时读取同一个 String 对象,不用担心其中一个线程修改它的值,省去了同步锁的开销,提升并发性能。
4. 哈希值缓存(提升哈希集合性能)

String 的hashCode()方法会缓存哈希值(String 类中有一个private int hash成员变量存储哈希值):

  • 因为 String 不可变,哈希值计算一次后就不会变化,后续调用hashCode()直接返回缓存值,无需重新计算;
  • 这对 HashMap、HashSet 等依赖哈希值的集合来说,能大幅提升查找效率。
源码佐证(Java 8):
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { // 存储字符的数组,被final修饰,且没有提供修改这个数组的方法 private final char value[]; // 缓存哈希值 private int hash; // Default to 0 // 构造方法(示例) public String(String original) { this.value = original.value; this.hash = original.hash; } // hashCode方法:缓存哈希值 public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } }

关键点:value数组被final修饰,且 String 类没有提供任何修改value的 public 方法(如 set、修改数组元素),确保了不可变性。

总结

Java 将 String 设计为不可变的核心原因可归纳为 3 点:

  1. 安全性:防止敏感数据被篡改,保障类加载、哈希表等核心功能的安全;
  2. 性能与内存优化:支撑字符串常量池复用,缓存哈希值提升集合操作效率;
  3. 线程安全:不可变特性让 String 天生支持多线程并发访问,无需同步锁。

补充:如果需要可变的字符串,Java 提供了StringBuilder(非线程安全)和StringBuffer(线程安全),它们的设计目的就是解决 String 拼接时创建大量临时对象的问题,本质是可变的字符序列。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 3:04:40

基于Spark的热门旅游景点数据分析系统的设计(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码_django+spider

基于Spark的热门旅游景点数据分析系统的设计(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码_djangospider python3.8djangosparkspidermysql5.7vue 管理员进入主页面&#xff0c;主要功能包括对个人中心、门票信息管理、名宿信…

作者头像 李华
网站建设 2026/6/4 13:11:34

揭秘Java面试中XML考点!这些地方你必须知道!

文章目录揭秘Java面试中XML考点&#xff01;这些地方你必须知道&#xff01;为什么面试官喜欢考XML&#xff1f;一、XML的基本概念什么是XML&#xff1f;XML的特点XML的常用场景二、Java中常用的XML解析方式1. DOM&#xff08;文档对象模型&#xff09;核心接口示例代码优点与缺…

作者头像 李华
网站建设 2026/6/6 1:44:58

书籍-斯坦因《西域考古记》

斯坦因《西域考古记》详细介绍 书籍基本信息 书名&#xff1a;西域考古记&#xff08;英文名&#xff1a;Serindia: Detailed Report of Explorations in Central Asia and Westernmost China&#xff09; 作者&#xff1a;马尔克奥莱尔斯坦因&#xff08;Aurel Stein&#xff…

作者头像 李华
网站建设 2026/6/4 12:39:23

【Linux命令大全】009.备份压缩之lha命令(实操篇)

【Linux命令大全】009.备份压缩之lha命令&#xff08;实操篇&#xff09; ✨ 本文为Linux系统备份压缩命令的全面汇总与深度优化&#xff0c;结合图标、结构化排版与实用技巧&#xff0c;专为高级用户和系统管理员打造。 (关注不迷路哈&#xff01;&#xff01;&#xff01;) 文…

作者头像 李华
网站建设 2026/6/4 12:39:07

均值为0,方差为1:数据的“标准校服”

均值为0&#xff0c;方差为1&#xff1a;数据的“标准校服” &#x1f31f; 一句话理解 均值为0 数据整体“居中”在0点 方差为1 数据的“波动程度”被统一标准化 &#x1f449; 两者结合 所有数据穿上“统一校服”&#xff0c;站在同一起跑线&#xff01; &#x1f50d; 分…

作者头像 李华