String 是 Java 使用率最高的類,其背後的常量池、不可變特性、創建時機差異構建了精妙的記憶體管理機制。本文從 class 文件結構、運行時記憶體佈局、五種創建方式、intern() 語義、七大經典案例全維度深度剖析。
CONSTANT_String_info{tag=8, string_index} → CONSTANT_Utf8_info{tag=1, length, bytes[]}。String.intern() 可運行時添加)。
private final char value[]; // JDK 8
// 或 private final byte[] value; // JDK 9+ Compact Strings
final 保證引用不變,無修改字符數組的方法,所有「修改」操作(substring, concat, replace...)均返回新 String。
| 方式 | 代碼 | 編譯期/運行期 | 存儲位置 | 備註 |
|---|---|---|---|---|
| 字面量 | String s = "abc"; | 編譯期 | String Pool | 同值唯一 |
| new String() | new String() | 運行期 | 堆 | 空串 |
| new String(String) | new String("abc") | 運行期 | 堆 | 實際創建 2 個對象(池中 "abc" + 堆新對象) |
| 常量拼接 | "ab" + "cd" | 編譯期優化 | String Pool | 等價於 "abcd" |
| 變量拼接 | "ab" + s | 運行期 | 堆 | 底層 StringBuilder |
String s = new String("abc").intern(); // 若池已有 "abc" 返回池中引用,否則將此實例加入池並返回
強制讓堆中 String 進入常量池,實現 == 比較複用。
// 1. 字面量複用
String s1 = "sss111", s2 = "sss111";
s1 == s2; // true
// 2. new 破壞複用
String s1 = new String("sss111");
String s2 = "sss111";
s1 == s2; // false
// 3. intern 修復
String s1 = new String("sss111").intern();
String s2 = "sss111";
s1 == s2; // true
// 4. 編譯期 vs 運行期拼接
String s1 = new String("111");
String s2 = "sss111";
String s3 = "sss" + "111"; // 編譯期常量 → 池
String s4 = "sss" + s1; // 運行期變量 → 堆
s2 == s3; // true
s2 == s4; // false
s2 == s4.intern(); // true
// 5. JLS 3.10.5 經典題
String hello = "Hello", lo = "lo";
hello == "Hello"; // true
hello == "Hel" + "lo"; // true (編譯期常量)
hello == "Hel" + lo; // false (運行期)
hello == ("Hel" + lo).intern(); // true
String s1 = new String("s1");
String s2 = new String("s1");
// 創建幾個 String 對象? 答:3 個(常量池 1 + 堆 2)
掌握 String 創建時機、常量池動態、intern() 語義,是避開 == 陷阱、優化記憶體佔用、通過高階面試的核心鑰匙。