首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

深度解析 Java String 不可變性:源碼、常量池與反射破壞實驗

     編輯:Java 相關知識

引言

「String 不可變」是 Java 面試必考點,但很少有人從源碼、內存模型、反射破壞三個維度完整驗證。本文帶你徹底看透。

一、兩種創建方式的內存差異

String s1 = "Hello World!";        // 常量池
String s2 = new String("Hello World!"); // 堆內存
  • 字面量賦值:優先檢查字符串常量池,存在則直接引用,否則創建並入池。
  • new String():強制在堆中創建新對象,不入常量池(除非調用 intern())。

二、== vs equals 驗證

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[] 從未被修改。

四、反射強行修改 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[]。

五、實戰建議

  1. 頻繁拼接字符串請使用 StringBuilder(單線程)或 StringBuffer(多線程)。
  2. 理解 intern() 可手動將堆對象放入常量池,節省內存。
  3. 不要嘗試在生產代碼中用反射修改 String,會破壞字符串常量池一致性,導致不可預期的 Bug。

總結

String 不可變是 Java 設計的基石,保證了線程安全、哈希值穩定、常量池共享。源碼層面通過 final char[] + 防禦性拷貝實現;反射可破壞但屬違規操作。日常開發遵循「把 String 當不可變對象用」即可。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved