可以把 String 理解为 Java 中用来表示文本的数据类型,它不是一个简单的数据类型(int 或 char),而是一个对象。

下面我们从几个核心方面来深入理解 String。
String 的核心特性
理解 String,首先要记住它的四个最关键特性:
a) 不可变性
这是 String 最重要的特性,一旦一个 String 对象被创建,它的内容(即它所包含的字符序列)就不能被改变。
任何看似修改 String 的操作,实际上都是创建了一个新的 String 对象,而原来的对象保持不变。

示例:
String s1 = "Hello"; String s2 = s1; // s2 和 s1 指向同一个对象 // 看似修改了 s1,但实际上: // 1. JVM 在内存中创建了一个新的 String 对象 "World"。 // 2. 让 s1 这个引用变量指向这个新对象。 // 3. 原来那个 "Hello" 对象如果没有其他引用指向它,就会被垃圾回收器回收。 s1 = "World"; System.out.println(s1); // 输出: World System.out.println(s2); // 输出: Hello (s2 仍然指向原来的 "Hello" 对象)
为什么 String 要设计成不可变?
- 线程安全不能被改变,所以多个线程可以同时访问一个
String对象而无需担心数据被篡改,不需要同步操作,提高了效率。 - 安全:在处理如数据库用户名、密码、文件路径等敏感信息时,由于其不可变性,这些信息在内存中不会被意外修改,更安全。
- 性能优化(字符串常量池):这是 JVM 的一种优化机制,由于
String不可变,JVM 可以将字面量(如"abc")创建的字符串对象缓存起来,下次再使用"abc"时,会直接从常量池中引用,而不是创建新对象,从而节省内存。
b) 对象而非基本类型
String 是一个类,属于 java.lang 包。String 变量实际上是一个对象的引用,它指向存储在堆内存中的 String 对象。
String str = "abc"; // "abc" 是一个 String 对象,str 是指向这个对象的引用。
c) 字符串常量池
这是一个特殊的内存区域,位于方法区(在 JDK 7 及之后版本,它被移到了堆内存中),它专门用来存储字符串字面量。

当使用双引号 创建字符串时,JVM 会首先检查常量池中是否已经存在该字符串。
- 如果存在:直接返回池中对象的引用。
- 如果不存在:创建一个新的字符串对象,并将其放入常量池,然后返回引用。
示例:
String s1 = "hello"; // JVM 在常量池中创建 "hello" 对象,s1 指向它 String s2 = "hello"; // JVM 发现常量池中已有 "hello",直接让 s2 指向同一个对象 System.out.println(s1 == s2); // 输出 true,因为 s1 和 s2 引用的是同一个对象
注意:使用 new 关键字创建字符串,不会检查常量池,而是直接在堆内存中创建一个新的对象。
String s3 = new String("hello"); // 在堆内存中创建一个新的 "hello" 对象
String s4 = new String("hello"); // 又在堆内存中创建另一个新的 "hello" 对象
System.out.println(s3 == s4); // 输出 false,因为 s3 和 s4 指向的是堆中两个不同的对象
d) final 类
String 类被声明为 final,这意味着:
- 不能被继承:你不能创建一个
String的子类,这保证了所有String对象的行为都是一致的,防止有人通过继承来破坏其不可变性。 - 所有方法都是“固定”的:
String类提供的方法集是稳定和不变的。
String 的常用方法
String 类提供了非常丰富的方法来操作字符串。
| 方法 | 描述 | 示例 |
|---|---|---|
length() |
返回字符串的长度 | "abc".length() 返回 3 |
charAt(int index) |
返回指定索引处的字符 | "abc".charAt(1) 返回 'b' |
substring(int beginIndex) |
返回从 beginIndex 开始到末尾的子串 |
"hello".substring(1) 返回 "ello" |
substring(int beginIndex, int endIndex) |
返回从 beginIndex 开始到 endIndex-1 的子串 |
"hello".substring(1, 3) 返回 "el" |
concat(String str) |
将指定的字符串连接到当前字符串的末尾(创建新对象) | "a".concat("b") 返回 "ab" |
toUpperCase() / toLowerCase() |
将字符串转换为大写/小写(创建新对象) | "aBc".toUpperCase() 返回 "ABC" |
trim() |
去除字符串首尾的空格(创建新对象) | " a b c ".trim() 返回 "a b c" |
equals(Object anObject) |
比较两个字符串的是否相等(区分大小写) | "abc".equals("ABC") 返回 false |
equalsIgnoreCase(String anotherString) |
比较两个字符串的内容是否相等(不区分大小写) | "abc".equalsIgnoreCase("ABC") 返回 true |
compareTo(String anotherString) |
按字典顺序比较两个字符串,返回一个整数值 | "abc".compareTo("abd") 返回 -1 |
contains(CharSequence s) |
判断字符串是否包含指定的字符序列 | "hello".contains("ell") 返回 true |
indexOf(String str) |
返回子字符串第一次出现的索引,未找到则返回 -1 |
"hello".indexOf("l") 返回 2 |
replace(char oldChar, char newChar) |
替换所有出现的字符(创建新对象) | "hello".replace('l', 'p') 返回 "heppo" |
String、StringBuilder 和 StringBuffer 的区别
在 Java 中,当你需要频繁修改字符串内容时,直接使用 String 会因为频繁创建新对象而导致性能问题,这时就需要 StringBuilder 和 StringBuffer。
| 特性 | String |
StringBuilder |
StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 线程安全(不可变天生安全) | 非线程安全 | 线程安全 |
| 性能 | 频繁修改时性能差 | 性能最好(无同步开销) | 性能比 StringBuilder 稍差(有同步开销) |
| 使用场景 | 存储和操作不变的文本 | 单线程环境下,进行大量字符串拼接、修改操作 | 多线程环境下,进行大量字符串拼接、修改操作 |
何时使用?
- 如果你的字符串内容不会改变,或者只改变一次,直接使用
String。 - 如果你在单线程环境中需要频繁地拼接、修改字符串,使用
StringBuilder(性能更高)。 - 如果你在多线程环境中需要频繁地拼接、修改字符串,为了保证数据安全,使用
StringBuffer。
示例:
// 使用 String (效率低)
String result = "";
for (int i = 0; i < 1000; i++) {
result = result + "a"; // 每次循环都创建一个新的 String 对象
}
// 使用 StringBuilder (效率高)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("a"); // 在原有的对象上进行修改,不创建新对象
}
String result = sb.toString();
String是什么? 它是 Java 中用于表示文本的不可变对象。- 核心特性:不可变性、是对象、存在于字符串常量池、是final类。
- 如何创建? 可以通过字面量 或
new关键字创建,两者在内存中的行为不同。 - 如何操作?
String类提供了大量方法来获取子串、拼接、比较、查找等,但所有方法都会返回一个新的String对象。 - 性能考量:对于频繁的字符串修改,应使用
StringBuilder(单线程)或StringBuffer(多线程)来代替String,以避免不必要的对象创建和垃圾回收开销。
