這三個問題是區分「會寫 Java 語法」與「理解 Java 物件模型」的分水嶺。若自以為超越初學者卻答不上來,請勇敢回歸基礎,重新建立正確心智模型。
String s = "Hello world!";
錯誤認知:「聲明了一個內容為 Hello world 的 String 物件。」
正確認知:聲明了一個名為 s、型別為 String 的參考變數(reference variable),它指向一個內容為 "Hello world!" 的 String 物件。
String s = "Hello";
String string = s; // 只是複製參考值(記憶體地址)
// 此時 s 和 string 指向同一個物件
// 堆上只有「一個」"Hello" 字串物件
Java 無指標運算,參考變數本質是「受管制的指標」,只能指向特定型別物件或 null。
int a = 10, b = 10;
a == b // true,基本型別比值
String a = new String("foo");
String b = new String("foo");
a == b // false!兩個 new 產生兩個不同物件,地址不同
// Object 預設實作
boolean equals(Object o) { return this == o; } // 同 ==
// String 覆寫:逐字元比對
// Integer、Date 等也覆寫為值比較
class Monster {
private String content;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Monster)) return false;
Monster m = (Monster) o;
return Objects.equals(content, m.content); // 由類別設計者定義「相等」
}
// 覆寫 equals 必須同步覆寫 hashCode()!
@Override public int hashCode() { return Objects.hash(content); }
}
鐵律:若打算放入 HashSet/HashMap/HashTable,必須正確覆寫 equals() 與 hashCode(),否則會出現「明明相等卻找不到」的詭異 Bug。
String s = "Hello";
s = s + " world!";
答案:原物件沒變,s 換指向新物件。
"Hello" 在字串常量池/堆建立物件 A,s → As + " world!" 觸發 StringBuilder 內部建構新字串,產生物件 B("Hello world!")// 差:每次迴圈產生新 String,產生大量暫存物件
String s = "";
for (int i = 0; i < 10000; i++) s += i;
// 好:單一 StringBuilder,最終 toString()
StringBuilder sb = new StringBuilder(50000); // 預估容量避免擴容
for (int i = 0; i < 10000; i++) sb.append(i);
String result = sb.toString();
// 好:所有實例共用同一個 "Initial Value" 物件
s = "Initial Value";
// 差:每次 new 強制建立新物件,浪費記憶體且無意義
s = new String("Initial Value");
字串字面值("...")內容相同則指向同一物件;new String() 永遠建新物件。
hashCode() 只需算一次,Map 鍵值查找極快。代價:每次修改需建新物件。Java 提供 StringBuilder(非同步)與 StringBuffer(同步)作為可變對應版本。
| 觀念 | 核心口訣 |
|---|---|
| 宣告 | 宣告的是參考,不是物件 |
| == vs equals | == 比地址,equals 比內容(由類別定義) |
| String 不變 | 變的是參考指向,不是物件本體;常用字面值、拼接用 StringBuilder |
徹底搞懂這三點,Java 物件模型、集合框架、多線程同步的基石就穩固了。