String 是 Java 使用頻率最高的類,也是面試高發區。本文從源碼、內存模型、字節碼角度徹底講透 String 的不可變性、常量池機制、拼接性能、比較正確姿勢。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; // JDK 8 及之前
// private final byte[] value; // JDK 9+ Compact Strings
private final int hash; // 默認 0,惰性計算緩存
}
final class:不可被繼承,保證行為不可被子類覆寫破壞。final char[] value:引用不可變,數組內容不可變(無修改方法),保證線程安全、哈希值穩定、可安全共享。String a = "abc123"; a.substring(3); // 返回新 String "123",a 仍為 "abc123" System.out.println(a); // 輸出 abc123
所有「修改」方法 (substring、replace、concat、toUpperCase 等) 均返回新實例,原對象不變。
String s1 = "abcd";
String s2 = "abcd"; // s1 == s2 → true (同一常量池引用)
String s3 = "ab" + "cd"; // 編譯期常量折疊優化 → "abcd",s3 == s1 → true
String s4 = new String("abcd"); // 堆中新對象,s4 == s1 → false
String s5 = s4.intern(); // 手動入池,返回常量池引用,s5 == s1 → true
面試題:String a = new String("a"); 創建幾個對象?
"a":1 個 (堆中 new 對象)。"a":2 個 (常量池 "a" + 堆中 new 對象)。| 場景 | 推薦方式 | 原理 |
|---|---|---|
| 少量拼接 (已知行數) | + 操作符 | 編譯期優化為 StringBuilder.append,字面量常量折疊 |
| 大量/循環拼接 | StringBuilder | 內部 char[] 擴容 (默認 16,2倍),避免產生大量中間 String 對象 |
| 多線程環境 | StringBuffer | 方法同步,線程安全,性能略低於 Builder |
// 預估大小初始化,減少擴容 StringBuilder sb = new StringBuilder(100); for (int i = 0; i < 100; i++) sb.append(i); String result = sb.toString();
// ❌ 錯誤:比較引用地址
if (str1 == str2) { ... }
// ✅ 正確:比較內容值
if (str1 != null && str1.equals(str2)) { ... }
// ✅ 更安全:常量在左防 NPE
if ("expected".equals(str1)) { ... }
// ✅ 高性能:長度判空
if (str1 != null && str1.length() == 0) { ... } // 空串判斷
if (str1 == null || str1.isEmpty()) { ... } // JDK 6+ isEmpty()
equals 源碼:先比引用 ==,再比長度,最後逐字符比較。
final String bb = "b"; // 編譯期常量
String b = "a" + bb; // 編譯期優化為 "ab",入常量池
System.out.println("ab" == b); // true
String bb2 = getBB(); // 運行期賦值,非常量
String b2 = "a" + bb2; // 運行期 new StringBuilder().append("a").append(bb2).toString()
System.out.println("ab" == b2); // false
結論:final 修飾且編譯期可確定值的變量視為常量,參與常量折疊。
StringBuilder,並預估容量。equals,== 僅用於枚舉、常量池引用、同步鎖對象身份判斷。String.intern() 濫用 (JDK 7+ 池在堆,但仍可能佔用大量內存,需配置 -XX:StringTableSize)。char[] 代替 String,用完手動清零。