核心区别:String vs char[]
虽然 String 在底层是通过字符数组实现的,但它们在 Java 中是完全不同的两个概念。

| 特性 | String |
char[] |
|---|---|---|
| 本质 | 一个不可变的类 | 一个可变的数组 |
| 存储位置 | 存储在字符串常量池或堆内存中。 | 作为对象,存储在堆内存中。 |
| 功能丰富度 | 提供了大量丰富的方法来处理字符串(如 length(), indexOf(), split(), matches() 等)。 |
功能相对基础,主要是对字符数组的操作(如 length, get, set)。 |
| 安全性 | 线程安全,因为不可变,所以多个线程可以同时访问一个 String 而无需同步。 |
非线程安全,在多线程环境下需要额外同步机制来保证安全。 |
| 性能 | 频繁的修改操作会创建大量新对象,可能导致性能下降和内存浪费(GC 压力)。 | 修改效率高,因为是在原数组上进行操作,不会创建新对象。 |
String 与 char[] 的相互转换
1 char[] 转 String
有几种方法可以实现,其中最常用和推荐的是使用 String 的构造函数。
使用构造函数(推荐)
这是最直接、最清晰的方式。
char[] chars = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
// 使用 String(char[]) 构造函数
String str1 = new String(chars);
System.out.println(str1); // 输出: Hello World
// 使用 String(char[], offset, count) 构造函数,截取部分字符
// 从索引 2 开始,取 3 个字符
String str2 = new String(chars, 2, 3);
System.out.println(str2); // 输出: llo
使用 String.valueOf() (静态方法)

这个方法更简洁,功能上等同于构造函数。
char[] chars = {'J', 'a', 'v', 'a'};
String str = String.valueOf(chars);
System.out.println(str); // 输出: Java
注意: 不要使用 toString() 方法
char[] 数组继承自 Object,其 toString() 方法返回的是类似 "[C@15db9742" 的内存地址表示,而不是字符序列。
char[] chars = {'J', 'a', 'v', 'a'};
System.out.println(chars.toString()); // 输出类似 [C@15db9742,这是错误的
2 String 转 char[]
使用 String 类的 toCharArray() 方法即可。
String str = "Java Programming";
// 将 String 转换为 char[]
char[] chars = str.toCharArray();
// 打印字符数组
System.out.println(Arrays.toString(chars));
// 输出: [J, a, v, a, , P, r, o, g, r, a, m, m, i, n, g]
// 访问特定字符
System.out.println("第一个字符是: " + chars[0]); // 输出: J
System.out.println("最后一个字符是: " + chars[chars.length - 1]); // 输出: g
为什么 String 要设计成不可变?
String 的不可变性是 Java 设计中的一个核心决策,主要有以下几个原因:

-
线程安全
- 因为
String对象一旦创建就不能改变,所以它在任何环境下都是线程安全的,多个线程可以同时读取一个String而无需担心数据被其他线程修改,这对于在并发编程中广泛使用的字符串至关重要。
- 因为
-
哈希码缓存
String对象的hashCode()在对象创建时就被计算并缓存了,因为内容不变,所以哈希码也永远不变,这使得String非常适合作为HashMap、HashSet和Hashtable等哈希表的键。String可变,那么它的哈希码也可能改变,这会导致在哈希表中找不到它,从而破坏数据结构。
-
字符串常量池
- 为了提高性能和减少内存占用,Java 使用了一个特殊的内存区域——字符串常量池。
- 当你创建一个字符串字面量(如
String s = "hello";)时,JVM 首先会在池中查找是否已经存在"hello",如果存在,就直接返回引用;如果不存在,就创建一个并放入池中。 - 因为
String不可变,JVM 可以安全地让多个变量引用池中的同一个字符串对象,而不用担心一个变量的修改会影响另一个。String可变,这种机制将无法实现。
-
安全性
- 许多 Java 的安全类(如加载器、安全管理器)都使用
String来表示文件路径、类名、权限等信息。String是可变的,恶意代码可以修改这些字符串,从而绕过安全检查,一个不可变的文件路径字符串可以安全地传递给不同的模块,而不必担心它被意外或恶意地修改为指向敏感文件。
- 许多 Java 的安全类(如加载器、安全管理器)都使用
实际应用场景示例
场景1:需要频繁修改字符内容
错误/低效的做法:使用 String
// 错误示范:频繁修改 String,会创建大量新对象,性能差 String text = "Hello"; text += " "; text += "World"; text += "!"; // 这段代码实际上创建了 4 个 String 对象,并产生了 3 次不必要的对象创建和垃圾回收。
正确的做法:使用 StringBuilder 或 StringBuffer
StringBuilder 内部就是一个可变的字符数组,专门用于处理字符串的拼接和修改。
// 正确示范:使用 StringBuilder,性能高
StringBuilder sb = new StringBuilder(); // 内部维护一个 char[]
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append("!");
String result = sb.toString();
System.out.println(result); // 输出: Hello World!
场景2:需要随机访问和修改单个字符
正确的做法:使用 char[]
char[] name = {'J', 'a', 'v', 'a'};
// 修改第二个字符为大写
name[1] = 'A'; // 直接在原数组上修改,非常高效
String newName = new String(name);
System.out.println(newName); // 输出: JAvA
如果用 String,每次修改都需要创建新对象,非常低效。
| 需求 | 推荐的数据结构 | 原因 |
|---|---|---|
| 存储文本,且内容不变 | String |
利用不可变性带来的线程安全、性能优化(常量池)和安全性。 |
| 需要频繁拼接、修改字符串 | StringBuilder (单线程) / StringBuffer (多线程) |
内部使用可变字符数组,修改效率高,避免创建大量临时对象。 |
| 需要随机访问和修改单个字符 | char[] |
数组的特性,可以通过索引直接访问和修改,效率最高。 |
需要将字符串作为键在 HashMap 中使用 |
String |
不可变性保证了哈希码的稳定,使其成为完美的键。 |
理解 String 和 char[] 的区别和联系,是编写高效、健壮的 Java 代码的基础。
