String str = "abc" 與 String str = new String("abc") 看似等價,實則在記憶體分配、物件建立、效能特性上有本質差異。本文從 JVM 記憶體模型(棧/堆/方法區)、字串常量池、享元模式、不可變特性等維度深度剖析。
| 區域 | 存放內容 | 特性 |
|---|---|---|
| 棧 | 基本型別區域變數、物件參考、方法呼叫框架 | 速度最快(僅次暫存器)、大小/生命週期固定、執行緒私有 |
| 堆 | 所有 new 出來的物件實例、陣列 | 動態分配、GC 管理、存取較慢、全執行緒共享 |
| 方法區/常量池 | 類別中繼資料、常量、字串常量池 | 邏輯上屬堆一部分、JDK 7+ 字串常量池移至堆 |
int, short, long, byte, float, double, boolean, char — 直接存字面值於棧,無物件實例。Integer, String, Double 等 — new 建立於堆,JDK 5+ 支援自動裝箱/拆箱。String str = "abc" 內部機制"abc" 放入 字串常量池(Class 檔常量表 → 類載入時駐留)。str 參考變數於棧建立。"abc":
str(共享)。String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2); // true,同一常量池物件
s1 = "bcd";
System.out.println(s1 == s2); // false,s1 指向新常量 "bcd"
System.out.println(s2); // abc,s2 不受影響
String s3 = s1;
String s4 = "bcd";
System.out.println(s1 == s4); // true,常量池共享
new String("abc") 強制建立新物件"abc",堆上必定建立新 String 實例。"abc" 仍會在常量池建立/駐留(若不存在)。== 比較永遠為 false(不同記憶體位址)。String s1 = new String("abc");
String s2 = "abc";
System.out.println(s1 == s2); // false
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s3 == s4); // false,兩個獨立堆物件
concat、replace、substring、賦新值)都會建立新物件,原物件等待 GC。String str = "abc"; str = "bcd"; 實際上是建立新參考指向常量池 "bcd",非修改原物件。StringBuilder (非同步) 或 StringBuffer (同步)。字串常量池是 Flyweight Pattern 經典應用:共享相同字面值減少記憶體佔用。String.intern() 可手動將堆上字串駐入常量池(JDK 7+ 若常量池已存在則返回池中參考,否則將堆物件參考存入池)。
| 場景 | 正確做法 | 錯誤做法 |
|---|---|---|
| 比較字串內容 | str1.equals(str2) 或 Objects.equals(str1, str2) | str1 == str2 |
| 比較是否同一物件 | str1 == str2 | str1.equals(str2) (效能較差) |
| 忽略大小寫比較 | str1.equalsIgnoreCase(str2) | 手動 toLowerCase 再 equals |
String s = "abc":享受常量池共享、編譯期常量折疊、效能最佳。new String():除非刻意需要獨立物件(極少見),否則增加堆壓力與 GC 負擔。== 比參考位址,equals 比內容。PermGen 移除,intern() 行為調整,大量駐留需關注堆大小。「清醒認識『建立參考』與『建立物件』的差異,是排除難以發現 Bug 的關鍵。」