String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
答案: 每行創建 **2 個對象** (堆對象 + 常量池字面量 "abc",但字面量只會在類加載時創建/入池一次)。
圖解:
`s1`、`s2` 指向不同堆對象,`==` 比較地址 → `false`。
String s1 = "abc"; StringBuffer s2 = new StringBuffer(s1); System.out.println(s1.equals(s2)); // false
答案:false。`String.equals(Object)` 源碼關鍵:
public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String) { // 關鍵:必須是 String 實例
String anotherString = (String) anObject;
// 逐字符比較...
}
return false;
}
`StringBuffer` 不是 `String`,直接返回 `false`。教訓:**equals 契約要求類型一致**。
答案:3 個 —— 2 個堆對象 (s1、s2) + 1 個常量池字面量 "abc"。
String s1 = "abc";
String s2 = new String("abc");
s2.intern();
System.out.println(s1 == s2); // false
答案:false。`intern()` 返回常量池引用,**但未賦值給 s2**,s2 仍指向堆對象。
修正版:
String s1 = "abc";
String s2 = new String("abc");
s2 = s2.intern(); // 關鍵:重新賦值
System.out.println(s1 == s2); // true
String s = new String("a") + new String("b"); // "ab" 堆對象
String s2 = s.intern(); // JDK 6: 複製到永久代常量池,返回新對象地址 ≠ s
// JDK 7+: 在堆常量池記錄引用,返回 s 自己的地址 == s
System.out.println(s == s2); // JDK 6: false, JDK 7+: true
**根因:** JDK 7 將常量池從永久代移至堆,`intern()` 不再複製對象,僅在常量池登記引用。
| 類 | 可變性 | 線程安全 | 性能 | 適用場景 |
|---|---|---|---|---|
| String | 不可變 | 不可變即線程安全 | 拼接產生大量臨時對象 | 常量、少量拼接、鍵值、傳遞參數 |
| StringBuilder | 可變 | 非線程安全 | 最快 (無同步開銷) | **單線程** 大量拼接、構建 SQL/JSON/日誌 |
| StringBuffer | 可變 | 線程安全 (synchronized) | 較慢 | 多線程共享字符串構建器 (較少見) |
深入理解 String 內存模型,不僅是面試必備,更是寫出高性能、低 GC 壓力 Java 代碼的基石。記住:**不可變是特性,常量池是優化,工具類選型是藝術**。