「String 不可變」是 Java 面試必考點,但很少有人從源碼、內存模型、反射破壞三個維度完整驗證。本文帶你徹底看透。
String s1 = "Hello World!"; // 常量池
String s2 = new String("Hello World!"); // 堆內存
new String():強制在堆中創建新對象,不入常量池(除非調用 intern())。String s1 = "Hello World!";
String s2 = "Hello World!";
String s3 = new String("Hello World!");
String s4 = new String("Hello World!");
System.out.println(s1 == s2); // true 常量池同一引用
System.out.println(s1 == s3); // false 堆 vs 常量池
System.out.println(s3 == s4); // false 兩個堆對象
System.out.println(s1.equals(s3)); // true 內容相同
JDK 源碼中 String 核心字段:
public final class String implements ... {
private final char value[]; // final 修飾,引用不可變
// ...
}
以 replace(char oldChar, char newChar) 為例:
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value;
while (++i < len) {
if (val[i] == oldChar) break;
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0; j < i; j++) buf[j] = val[j];
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true); // 返回新對象!
}
}
return this; // 無變化返回自身
}
所有修改操作(substring、concat、toUpperCase 等)均返回新 String 對象,原對象 value[] 從未被修改。
String hello = "Hello World!";
String xx = new String("Hello World!");
String yy = "Hello World!";
// 獲取私有 value 字段
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] helloValue = (char[]) field.get(hello);
char[] xxValue = (char[]) field.get(xx);
char[] yyValue = (char[]) field.get(yy);
// 驗證三者最初指向同一數組(常量池優化)
System.out.println(helloValue == xxValue); // true
System.out.println(helloValue == yyValue); // true
// 修改數組內容
helloValue[5] = '^'; // 將空格替換為 ^
// 驗證:所有引用該數組的字符串內容均被修改!
System.out.println(hello); // Hello^World!
System.out.println(xx); // Hello^World!
System.out.println(yy); // Hello^World!
結論:String 的不可變性是語言級約束(final + 無 setter + 方法返回新對象),但在 JVM 底層,char[] value 仍可被反射修改。這也揭示了常量池的極致優化:相同內容的字符串字面量、new String() 內部數組可能共享同一 char[]。
StringBuilder(單線程)或 StringBuffer(多線程)。intern() 可手動將堆對象放入常量池,節省內存。String 不可變是 Java 設計的基石,保證了線程安全、哈希值穩定、常量池共享。源碼層面通過 final char[] + 防禦性拷貝實現;反射可破壞但屬違規操作。日常開發遵循「把 String 當不可變對象用」即可。