String的基本概念与特性String的创建与初始化String的常用方法详解String不可变性的深入理解String、StringBuilder和StringBuffer的区别与选择String的其他高级用法
String 的基本概念与特性
String 在 Java 中代表一个字符序列,也就是我们常说的字符串。

核心特性:不可变性
这是 String 最重要、最核心的特性,一旦一个 String 对象被创建,它的内容(即字符序列)就不能被修改。
- 任何对字符串的修改操作(如
replace,substring,concat等)都不会改变原始的String对象。 - 这些操作实际上会创建并返回一个新的
String对象,新对象包含了修改后的内容,而原始对象保持不变。
为什么设计成不可变?
- 线程安全:不可变对象天生就是线程安全的,因为多个线程无法同时修改它,避免了同步开销。
- 安全:在 Java 中,
String被广泛用于表示文件路径、网络地址、数据库 URL 等敏感信息。String可变,这些信息可能会在程序运行过程中被恶意修改,带来安全隐患。 - 性能优化(字符串常量池):由于不可变,JVM 可以对
String对象进行缓存和重用,这就是字符串常量池的原理,可以大大节省内存。
String 的创建与初始化
创建 String 对象主要有两种方式。
使用字面量
String str1 = "Hello"; String str2 = "Hello";
这种方式创建的字符串会被存放在字符串常量池 中,由于 str1 和 str2 的内容相同,JVM 会让它们指向常量池中的同一个对象,str1 == str2 的结果为 true。
System.out.println(str1 == str2); // 输出 true
使用 new 关键字
String str3 = new String("Hello");
String str4 = new String("Hello");
这种方式会在堆内存 中创建新的 String 对象,即使内容相同,new 也会创建不同的对象实例。
System.out.println(str3 == str4); // 输出 false // 因为它们是堆上两个不同的对象
intern() 方法:
intern() 方法会尝试将字符串对象放入常量池,如果常量池中已存在该字符串,则返回常量池中的引用;否则,将当前字符串加入常量池并返回其引用。
String s1 = new String("Hello");
String s2 = s1.intern(); // s2 现在指向常量池中的 "Hello"
String s3 = "Hello";
System.out.println(s1 == s2); // false, s1在堆上,s2在常量池
System.out.println(s2 == s3); // true, s2和s3都指向常量池中的同一个对象
String 的常用方法详解
以下是 String 类中最常用的一些方法,建议熟练掌握。
1 长度与访问
length(): 返回字符串的长度(字符数)。String s = "Java"; System.out.println(s.length()); // 输出 4
charAt(int index): 返回指定索引处的字符。System.out.println(s.charAt(0)); // 输出 'J'
getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin): 将字符串的一部分复制到字符数组中。
2 查找
indexOf(char/String): 从字符串开头查找,返回字符或子串第一次出现时的索引,找不到则返回-1。String text = "Hello world, hello Java"; System.out.println(text.indexOf('o')); // 输出 4 System.out.println(text.indexOf("hello")); // 输出 13lastIndexOf(char/String): 从字符串末尾查找,返回字符或子串最后一次出现时的索引。System.out.println(text.lastIndexOf('o')); // 输出 7contains(CharSequence): 判断字符串是否包含指定的字符序列,返回boolean。System.out.println(text.contains("Java")); // 输出 true
3 比较
equals(Object): 比较两个字符串的是否完全相同,区分大小写。String s1 = "hello"; String s2 = "Hello"; System.out.println(s1.equals(s2)); // 输出 false
equalsIgnoreCase(String): 比较两个字符串的内容是否相同,不区分大小写。System.out.println(s1.equalsIgnoreCase(s2)); // 输出 true
compareTo(String): 字典序比较,如果两个字符串相等,返回0;如果小于参数字符串,返回负数;否则返回正数。String a = "apple"; String b = "banana"; System.out.println(a.compareTo(b)); // 输出负数
startsWith(String) / endsWith(String): 判断字符串是否以指定的前缀/后缀开头或结尾。System.out.println(text.startsWith("Hello")); // 输出 true System.out.println(text.endsWith("Java")); // 输出 true
4 修改与创建新字符串(注意:原字符串不变)
substring(int beginIndex): 返回从beginIndex开始到末尾的子串。substring(int beginIndex, int endIndex): 返回从beginIndex开始到endIndex(不包括)的子串。String path = "/usr/local/bin"; System.out.println(path.substring(5)); // 输出 local/bin System.out.println(path.substring(5, 10)); // 输出 local
concat(String): 连接两个字符串。String s = "Java"; String newS = s.concat(" is awesome!"); System.out.println(newS); // 输出 Java is awesome! System.out.println(s); // 输出 Java (原字符串未变)replace(char oldChar, char newChar) / replace(CharSequence target, CharSequence replacement): 替换字符或子串。String msg = "Hello World"; String newMsg = msg.replace('l', 'r'); // Herra Worrd System.out.println(newMsg);toLowerCase() / toUpperCase(): 转换为小写/大写。System.out.println("Java".toLowerCase()); // 输出 javatrim(): 去除字符串两端的空白字符(空格、制表符、换行等)。System.out.println(" Java ".trim()); // 输出 "Java"
5 分割与连接
split(String regex): 根据给定的正则表达式分割字符串,返回一个字符串数组。String csv = "apple,banana,orange"; String[] fruits = csv.split(","); // fruits 数组为 ["apple", "banana", "orange"]join(CharSequence delimiter, CharSequence... elements): (Java 8+) 将多个字符串用指定的分隔符连接起来。String result = String.join("-", "2025", "10", "27"); System.out.println(result); // 输出 2025-10-27
6 格式化
format(String format, Object... args): 格式化字符串,类似于 C 语言的printf。String name = "Alice"; int age = 30; String formatted = String.format("My name is %s and I am %d years old.", name, age); System.out.println(formatted); // 输出 My name is Alice and I am 30 years old.
String 不可变性的深入理解
让我们通过代码来直观感受不可变性:
String s = "original";
System.out.println("Original s: " + s); // original
// 调用 toUpperCase() 方法
s = s.toUpperCase();
// 注意:这里不是修改了 s 对象,而是创建了一个新的对象 "ORIGINAL"
// 然后让引用 s 指向了这个新对象
System.out.println("After modification: " + s); // ORIGINAL
内存示意图:
-
String s = "original";s -----> "original" (在常量池) -
s = s.toUpperCase();s.toUpperCase()创建了一个新的字符串对象"ORIGINAL"。- 引用
s不再指向"original",而是指向了新的"ORIGINAL"。 "original"对象如果没有其他引用,会被垃圾回收器回收。
s -----> "ORIGINAL" (在常量池) "original" (可能被回收)
String、StringBuilder 和 StringBuffer 的区别与选择
| 特性 | String |
StringBuilder |
StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 线程安全 | 非线程安全 | 线程安全 |
| 性能 | - (频繁修改性能差) | 高 (无同步开销) | 较低 (有同步开销) |
| 主要用途 | 存储不变的文本信息 | 单线程环境下进行大量字符串拼接、修改 | 多线程环境下进行大量字符串拼接、修改 |
如何选择?
-
如果字符串内容不需要改变:直接使用
String。String userName = "john_doe";
-
在单线程环境下,需要频繁修改字符串内容(如循环拼接):使用
StringBuilder,性能最好。// 错误示范:在循环中拼接字符串,效率极低 String result = ""; for (int i = 0; i < 1000; i++) { result += "a"; // 每次循环都创建新对象 } // 正确示范 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.append("a"); // 在原对象上修改,效率高 } String finalResult = sb.toString(); -
在多线程环境下,需要安全地修改字符串内容:使用
StringBuffer。StringBuffer的方法大多被synchronized修饰,保证了线程安全,但因此牺牲了一部分性能。- 在现代 Java 开发中,如果对性能要求极高且确实需要线程安全的字符串操作,有时会考虑使用外部锁来同步
StringBuilder,但绝大多数情况下,StringBuffer已经足够。
String 的其他高级用法
-
正则表达式:
String类提供了matches(),replaceAll(),split()等方法支持正则表达式,非常强大。String email = "test.user+123@example.com"; // 移除所有非字母数字的字符 String cleaned = email.replaceAll("[^a-zA-Z0-9]", ""); System.out.println(cleaned); // testuser123examplecom -
String与基本数据类型转换:String->int:Integer.parseInt("123")String->double:Double.parseDouble("12.34")int->String:String.valueOf(123)或123 + ""double->String:String.valueOf(12.34)
-
String与字符数组转换:String->char[]:toCharArray()char[]->String:new String(charArray)或String.valueOf(charArray)
String是 Java 中不可变的字符序列。- 创建方式有两种:字面量(存于常量池)和
new(存于堆)。 - 所有修改操作都会返回新对象,原对象不变,这是初学者最容易混淆的地方。
- 对于少量、静态的字符串,使用
String。 - 对于大量、动态的字符串拼接或修改,在单线程用
StringBuilder,多线程用StringBuffer。
熟练掌握 String 的用法是编写高质量 Java 代码的基础,希望这份详细的指南能帮助你!
