String系列





String系列

很多時候,我們都會認爲我們十分的瞭解String,久而久之,在使用過程中,就會踩一些坑。

從String的+實現原理引發的一系列問題

先來看一道題,你認爲結果如何?

        String a = "helloWord1";

        final String b = "helloWord";
        String F = "helloWord";

        String c = b + 1;
        String e = F + 1;
        System.out.println((a == c));
        System.out.println((a == e));
         // 獲取內存地址
		// System.out.println(System.identityHashCode(a));
		// System.out.println(System.identityHashCode(c));
		// System.out.println(System.identityHashCode(e));

我先不說結果,對的人自然對,不對的人自然不對,不過不要緊,你花一點兒時間聽我嘮叨完。

習慣了使用String str = str1 + str2,卻從未想過以前經常準備過的面試題:String類型是不可變。對呀,明明不可變,那這裏爲啥看起來好像可以變呢,是不是我們在進行一次str = str1,就該可以改變了呢,答案當然是否定的。

這裏講下這個+的原理:實際上底層(編譯器做的事)是使用StringBuilderJDK1.5之前是StringBuffer)的append來完成字符串的拼接,對於每一個 str1,則調用其String.valueOf(str1)獲取其值,最後的執行效果如:

String str = (new StringBuilder(String.valueOf(str1))).append(String.valueOf(str2)).toString()

看到這裏是不是對String類型是不可變的加深了理解了呢,因爲每次都是new StringBuilder(),所以這裏看似可變,其實每次都是new出的新對象,底層實際上是更改了棧內變量名指向的地址,使之指向堆中新的對象。(當然,String爲什麼不可變,原理不僅僅於此,源碼更能解釋String如何實現了不可變,感興趣的,繼續往下翻)

明白了+號實現原理,是不是可以舉一反三,請看以下代碼:

String str = null;
str += "a";
System.out.println(str);

你認爲輸出是什麼呢?

結果是:nulla

爲什麼呢,根據上述原理分析,在StringBuilder進行append之前,會對對象進行String.valueof(),那麼我們來翻下源碼:

    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

這下應該明白了,所以平時在開發中針對字符串的處理,一定要三思而後行。

String是不可變的

什麼是不可變對象

如果一個對象它被創建後,狀態不能改變,則這個對象被認爲是不可變的。

爲什麼不可變

String的底層實際上一個char數組,相信好奇心重的盆友都翻開過源碼,那麼我把他扒出來的目的只有一個,大家請看char數組是不是多了個修飾:final。

    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

被final修飾的變量在其初始化完成後,不可變。

補充一句,這裏的不可變是指引用變量所引用的地址不可變, 即一直引用同一個對象,但這個對象完全可以發生改變 。 例如某個指向數組的final引用,它必須從此至終指向初始化時指向的數組,但是這個數組的內容完全可以改變。

既然存在了final,那麼value引用的地址,肯定無法變了。再看看內容,貌似沒有任何一個方法,允許我們直接修改value的內容。到此已經可以證明:String不可變。

但是,String真的不可變嗎?

不死心呀,程序員可是能上天入地的,這點小事我就要從了嗎?客官彆着急,還真有法子打破String不可變的真理——反射,這個強大的功能,讓我們可以強行修改value的值,上例子:

        String str = "Hello World";
        System.out.println("修改前的str:" + str);
        System.out.println("修改前的str的內存地址" + System.identityHashCode(str));
        // 獲取String類中的value字段
        Field valueField = String.class.getDeclaredField("value");
        // 改變value屬性的訪問權限
        valueField.setAccessible(true);
        // 獲取str對象上value屬性的值
        char[] value = (char[]) valueField.get(str);
        // 改變value所引用的數組中的字符
        value[3] = '?';
        System.out.println("修改後的str:" + str);
        System.out.println("修改前的str的內存地址" + System.identityHashCode(str)); 

執行結果:

修改前的str:Hello World
修改前的str的內存地址1784662007
修改後的str:Hel?o World
修改前的str的內存地址1784662007

可以看到了,內存地址未變,但是內容變掉了。

總結:String類型是不可變的,但是在我們使用了反射之後,往往是可以打破這些原則。

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