一、什么是String?¶
在Java中,String是用于表示文本数据的类,它是不可变的(一旦创建,内容就无法修改)。正因为不可变,String在Java中被广泛用于存储和处理文本,比如用户输入、文件名、URL等。
二、String的创建方式¶
Java中创建String对象主要有两种方式,它们的内存行为不同,初学者容易混淆,需要重点区分。
1. 直接赋值(推荐)¶
String s1 = "hello";
String s2 = "hello";
- 原理:Java会先在字符串常量池(一种特殊的内存区域,用于复用相同的字符串常量)中查找是否存在
"hello"。 - 如果存在,
s1和s2直接指向常量池中的同一个"hello"对象。 - 如果不存在,会在常量池中创建新的
"hello",并让s1/s2指向它。 - 特点:相同常量会被复用,节省内存。例如,
s1和s2虽然是两个变量,但它们的引用相同(s1 == s2为true)。
2. new关键字创建¶
String s3 = new String("hello");
String s4 = new String("hello");
- 原理:无论常量池是否有
"hello",都会在堆内存中创建一个新的String对象,内容为"hello",且新对象的引用与常量池无关。 - 特点:即使内容相同,
s3和s4的引用也不同(s3 == s4为false)。因为new会强制新建堆对象,而直接赋值优先复用常量池。
对比直接赋值与new创建¶
| 方式 | 引用是否相同(相同内容) | 内存位置 | 是否复用常量池 |
|---|---|---|---|
| 直接赋值 | true(常量池复用) |
常量池 | 是 |
| new创建 | false(堆中新建) |
堆内存 | 否(但内容相同的常量会先放入常量池) |
三、String的拼接操作¶
拼接字符串是常用操作,Java提供了多种方式,需注意不同方式的性能和结果差异。
1. 使用+号拼接(最直观)¶
String a = "a";
String b = "b";
String c = a + b; // 结果:"ab"
- 原理:
+号在编译时会被编译器优化。 - 如果拼接的是全常量(如
"a" + "b"),会直接在常量池生成"ab"。 - 如果拼接中包含变量(如
a + b,a/b是变量),会在堆中创建新的String对象。 - 注意:频繁用
+号拼接(尤其是循环中)会创建大量临时对象,效率极低!
2. 使用concat()方法¶
String d = "a".concat("b"); // 结果:"ab"
- 原理:
concat()是String类的方法,返回拼接后的新字符串,原字符串不变(因为String不可变)。 - 示例:
String e = "hello";
String f = e.concat("world"); // f是"helloworld",e仍为"hello"
3. 使用StringBuilder/StringBuffer(高效拼接)¶
String不可变,每次拼接都会新建对象;而StringBuilder/StringBuffer是可变的字符序列,拼接时直接修改内部数组,效率更高。
- 区别:
- StringBuilder:非线程安全,单线程下效率更高(默认推荐)。
- StringBuffer:线程安全(方法加了synchronized),多线程场景使用。
示例:
StringBuilder sb = new StringBuilder();
sb.append("a"); // 拼接单个字符
sb.append("b"); // 拼接字符串
String result = sb.toString(); // 转换为String(最终结果)
四、String的比较操作¶
比较字符串时,==和equals()的作用完全不同,初学者常因混淆两者导致逻辑错误。
1. ==:比较对象引用(是否指向同一个对象)¶
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true(指向常量池同一个对象)
System.out.println(s1 == s3); // false(s3是堆中新建对象,引用不同)
2. equals():比较字符串内容(忽略引用)¶
String s1 = "hello";
String s3 = new String("hello");
System.out.println(s1.equals(s3)); // true(内容相同)
- 注意:
equals()只能用于非null字符串,否则会抛出NullPointerException(如null.equals("a"))。- 避免用
==代替equals()比较内容!
3. 正确比较空字符串¶
判断字符串是否为空时,推荐用isEmpty()或length() == 0,而非== ""或== null。
String s = "";
System.out.println(s.isEmpty()); // true(空字符串,但非null)
System.out.println(s.length() == 0); // true
System.out.println(s == ""); // true(内容相同,但不推荐)
五、常见问题与解决方法¶
1. 混淆==和equals()¶
错误场景:
String a = new String("hello");
String b = new String("hello");
if (a == b) { // 错误!==比较引用,a和b是不同对象
System.out.println("a和b相同");
} else {
System.out.println("a和b内容相同但不是同一个对象");
}
解决:用equals()比较内容:
if (a.equals(b)) { // 正确!比较内容
System.out.println("a和b内容相同");
}
2. 循环中频繁用+号拼接字符串¶
错误场景:
String result = "";
for (int i = 0; i < 1000; i++) {
result = result + i; // 每次循环新建String对象,效率极低!
}
解决:用StringBuilder替代+号:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // 直接修改内部数组,无额外对象创建
}
String result = sb.toString(); // 最终转换为String
3. 空字符串判断用== ""¶
错误场景:
String s = null;
if (s == "") { // 错误!null不能用== ""判断
System.out.println("空字符串");
}
解决:先判空,再比较内容:
if (s != null && s.isEmpty()) { // 推荐写法
System.out.println("空字符串");
}
六、总结¶
- 不可变性:
String一旦创建无法修改,任何拼接/修改操作都会生成新对象。 - 创建方式:直接赋值优先复用常量池,
new关键字强制新建堆对象。 - 拼接选择:少量拼接用
+或concat(),大量拼接(如循环)用StringBuilder。 - 比较规则:
==比较引用,equals()比较内容,避免混淆;空字符串用isEmpty()或length() == 0,并先判null。
掌握以上内容,就能避免大部分String相关的常见错误,写出高效清晰的Java代码。