Java String類

String的不可變性

Java.lang包下面的的String在平時很常見,我們都知道String是不可變類。簡單說來,不可變類的實例是不能被修改的,每個實例中包含的信息都必須在該實例創建的時候就提供出來,並且在對象的整個生存週期內固定不變。
在String類中,是通過一個不可變的字符數組來存儲對象的字符序列的,final關鍵字使這個變量是不可變的,所以Sting對象的內容也是不可變的。

private final char value[];

很多初學者對於不可變類還是很不能理解,因爲通過下面的代碼,看起來改變了字符串對象s。

public static void main(String[] args) {
        String s = "abc";
        System.out.println(s);
        s = "cde";
        System.out.println(s);
    }

輸出結果爲

abc
cde

其實這是因爲對String對象的創建和存儲機制不瞭解產生的誤解。下面我們來探討String的對象創建和初始化。

String的對象創建和初始化機制

字符串的聲明和初始化主要有以下兩種情況:
(1) 對於String s1=new String(“abc”)語句與String s2=new String(“abc”)語句,存在兩個引用對象s1、s2,兩個內容相同的字符串對象”abc”,它們在內存中的地址是不同的。只要用到new總會生成新的對象。

(2) 對於String s1 = “abc”語句與String s2 = “abc”語句,在JVM中存在着一個字符串池string pool,其中保存着很多String對象,並且可以被共享使用,s1、s2引用的是同一個常量池中的對象。當創建一個字符串常量的時候,例如String s = “abc”,會首先在字符串常量池中查找是否已經有相同的字符串被定義,它的判斷依據是String類equals(Object obj)方法的返回值,也就是判斷是否有相同的字符串序列。如果已經定義,則直接獲取對其的引用,此時不需要創建新的對象,如果沒有定義,則首先創建這個對象,然後把它加入到字符串池中,再將它的引用返回。

例如:

String s1=“abc”; //在常量區裏面存放了一個”abc”字符串對象
String s2=“abc”; //s2引用常量區中的對象,因此不會創建新的對象
String s3=new String(“abc”) //在堆中創建新的對象
String s4=new String(“abc”) //在堆中又創建一個新的對象

回到String對象的不可變特性

public static void main(String[] args) {
        String s = "abc";
        //在常量區中存放一個"abc"字符串對象,並把s指向這個對象
        System.out.println(s);
        s = "cde";
        //在string pool中查找是否存在"dec",不存在則創建這個對象,並把s指向這個對象
        System.out.println(s);
    }

通過上面的分析,我們可以知道,改變的只是s的指向,而“abc”和“cde”這兩個字符串對象並沒有改變,內存中字符串對象如下所示:

執行String s = “abc”後s的指向

執行String s = "abc"後s的指向

執行 s = “cde”後s的指向

執行 s = "cde"後s的指向

改變的只是s的指向,而字符串對象的內容並沒有發生改變。
另外,在進行字符串的對象創建時我們還可能會使用“+”來進行字符串的連接, 如果用+號來實現String的串接時有兩種情況:
1)僅當+號兩邊均爲字符串常量時,纔將其+後的結果當做字符串常量,且該結果直接放入String Pool;
2)若+號兩邊有一方爲變量時,+後的結果即當做非字符串常量處理(等同於new String()的效果)。

改變不可變的String對象

如果我們真的需要去改變String對象,那麼應該怎麼做呢?
這個時候就需要用到java的反射機制了,我們可以通過反射機制去獲取String對象的value數組,並修改value字符數組的相應內容,這樣子字符串對象也會相應的發生改變。代碼如下:

    public static void main(String[] args)  {


        try {
            String s = "abc";
            Field valuefield = String.class.getDeclaredField("value");
            valuefield.setAccessible(true);
            char[] value = (char[]) valuefield.get(s);
            value[1] = '_';
            System.out.println(s);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

輸出結果如下:

a_c
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章