在 JDK 原始碼中,String 類別主要包含以下關鍵欄位:
private final char value[]:用於儲存字符的字符陣列。private final int offset:儲存區中第一個被使用的索引位置。private final int count:字符串實際包含的字符數量。private int hash:緩存字符串的哈希碼,預設為 0。private static final long serialVersionUID:序列化版本號,保持跨版本相容性。final 修飾的欄位並不一定要在宣告時初始化,只要在物件初始化完成(構造器執行結束)前賦值即可。Java 允許在顯式初始化、初始化塊、構造器等任一階段對其賦值,編譯器僅檢查構造器結束前是否已確定賦值。
Java 記憶體分配涉及以下區域:
new 出來的物件儲存於此,受 GC 管理。static 修飾的靜態成員。String 常量(如 String s = "123")即存放於常量池。== 比較的是記憶體地址值。equals 預設也比較地址(依賴 hashCode),但 String 重寫後改為逐字符比較內容值。public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++]) return false;
}
return true;
}
}
return false;
}
核心法則:
new 出來的新物件(堆中分配)。String s1 = "hello"; String s2 = "world"; String s3 = "helloworld"; System.out.println(s3 == s1 + s2); // false:s1+s2 產生新物件 System.out.println(s3.equals(s1 + s2)); // true:內容相同 System.out.println(s3 == "hello" + "world"); // true:常量折疊,指向同一常量池對象
String a = "ab"; final String bb = "b"; // 編譯期進入常量池 String b = "a" + bb; // 編譯期常量折疊 -> "ab" System.out.println(a == b); // true // 若 final 變數由方法返回,則無法編譯期確定 final String bb2 = getBB(); // 運行期賦值 String b2 = "a" + bb2; // new 物件 System.out.println(a == b2); // false
當 final 修飾的變數值來自方法呼叫時,編譯器無法在編譯期完成常量折疊,導致拼接結果為新物件,地址比較返回 false。這類細節將在後續「編譯期確定」專題深入分析。