從字節碼和JVM的角度解析Java核心類String的不可變特性

凱倫說,公眾號ID: KailunTalk,努力寫出最優質的技術文章,歡迎關注探討。

1. 前言

最近看到幾個有趣的關于Java核心類String的問題。

  1. String類是如何實現其不可變的特性的,設計成不可變的好處在哪里。
  2. 為什么不推薦使用+號的方式去形成新的字符串,推薦使用StringBuilder或者StringBuffer呢。

翻閱了網上的一些博客和stackoverflow,結合自己的理解做一個匯總。

2. String類是如何實現不可變的

String類的一大特點,就是使用Final類修飾符。

A class can be declared final if its definition is complete and no subclasses are desired or required.

Because a final class never has any subclasses, the methods of a final class are never overridden .

Java SE 7 官方手冊中的定義如上,如果你認為這個類已經定義完全并且不需要任何子類的話,可以將這個類聲明為Final,Final類中的方法將永遠不會被重寫。

在Java中,String是被設計成一個不可變(immutable)類,一旦創建完后,字符串本身是無法通過正常手段被修改的。

private final char value[];      // 一旦初始化后,引用不能被修改

public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

選了substring方法來做一個代表,其他常見的涉及String操作的方法都是類似,如果你操作后的內容會和目前String中的內容不一致的話,那么都是重新創建一個新的String類返還,不會讓你去修改內部的內容。

將String類設計成Final類,能夠避免其方法被子類重寫,從而破壞了它本身方法的實現,進而破壞了不可變的特性。

2.1 String類設計成不可變的好處

我們都不是Java語言的設計者,不知道其為何一定要設計成不可變,試著做一些猜想。

  1. 可以實現多個變量引用JVM內存中的同一個字符串實例。見后文String Pool的介紹。
  2. 安全性,String類的用途實在太廣了,如果可以隨意修改的,是不是很恐怖。
  3. 性能,String大量運用在哈希的處理中,由于String的不可變性,可以只計算一次哈希值,然后緩存在內部,后續直接取就好了。如果String類是可變的話,在進行哈希處理的時候,需要進行大量的哈希值的重新計算。

這是結合個人理解和stackoverflow上看的匯總,我們來看看Java語言的爸爸James Gosling是怎么說的。

From a strategic point of view, they tend to more often be trouble free. And there are usually things you can do with immutables that you can't do with mutable things, such as cache the result. If you pass a string to a file open method, or if you pass a string to a constructor for a label in a user interface, in some APIs (like in lots of the Windows APIs) you pass in an array of characters. The receiver of that object really has to copy it, because they don't know anything about the storage lifetime of it. And they don't know what's happening to the object, whether it is being changed under their feet.

You end up getting almost forced to replicate the object because you don't know whether or not you get to own it. And one of the nice things about immutable objects is that the answer is, "Yeah, of course you do." Because the question of ownership, who has the right to change it, doesn't exist.

One of the things that forced Strings to be immutable was security. You have a file open method. You pass a String to it. And then it's doing all kind of authentication checks before it gets around to doing the OS call. If you manage to do something that effectively mutated the String, after the security check and before the OS call, then boom, you're in. But Strings are immutable, so that kind of attack doesn't work. That precise example is what really demanded that Strings be immutable.

這是James Gosling在2001年5月的一次訪談中,談到了不可變類和String,大意就是 他會更傾向于使用不可變類,它能夠緩存結果,當你在傳參的時候,使用不可變類不需要去考慮誰可能會修改其內部的值,這個問題不存在的。如果使用可變類的話,可能需要每次記得重新拷貝出里面的值,性能會有一定的損失。

老爺子還說了,迫使String類設計成不可變的另一個原因是安全,當你在調用其他方法,比如調用一些系統級操作之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過后,其內部的值被改變了,可能引起嚴重的系統崩潰問題,這是迫使String類設計成不可變類的重要原因。

2.2 String Pool

上文說了,設計成不可變后,可以多個變量引用JVM上同一塊地址,可以節省內存空間,相同的字符串不用重復占用Heap區域空間。

String test1 = "abc";
String test2 = "abc";

通常我們平時在使用字符串是,都是通過這種方式使用,那么JVM中的大致存儲就是如下圖所示。

兩個變量同時引用了String Pool中的abc,如果String類是可變的話,也就不能存在String Pool這樣的設計了。
在平時我們還會通過new關鍵字來生成String,那么新創建的String是否也會和上文中的示例一樣共享同一個字符串地址呢。

        String test1 = "abc";
        String test2 = "abc";
        String test3 = new String("abc");

答案是不會,使用new關鍵字會在堆區在創建出一個字符串,所以使用new來創建字符串還是很浪費內存的,內存結構如下圖所示。

2.3 不推薦使用+來拼裝字符串的原因。

首先我們來看這一段代碼,應該是之前寫代碼比較常見的。

String test1 = "abc";
String test2 = "abc";
String test3 = test1 + test2;

test3通過test1和test2拼接而成,我們看一下這個過程中的字節碼。

從以上圖我們可以看到,目前的JDK7的做法是,會通過新建StringBuilder的方式來完成這個+號的操作。這是目前的一個底層字節碼的實現,那么是不是沒有使用StringBuilder或者StringBuffer的必要了呢。還是有的,看下一個例子。

String test2 = "abc";
String test3 = "abc";

for (int i = 0; i < 5; i++) {
    test3 += test2;
}

在上述代碼中,我們還是使用+號進行拼接,但這次我們加了一個循環,看一下字節碼有什么變化。


每次循環都會創建一個StringBuilder,在末尾再調用toString返還回去,效率很低。繼續看下一個例子,我們直接使用StringBuilder,來做拼接。

String test2 = "abc";
// 使用StringBuilder進行拼接
StringBuilder test4 = new StringBuilder("abc");
for (int i = 0; i < 5; i++) {
    test4.append(test2);
}

每次循環體中只會調用之前創建的StringBuilder的append方法進行拼接,效率大大提高。

至于StringBuilder 的內部實現,諸位有興趣可以自己再去看一下,本質上也是一個char數組上的操作,和StringBuffer的區別在于,StringBuffer是有做同步處理的,而StringBuilder沒有。

3. 總結

本文主要探討了String類設計為Final修飾和不可變類的原因,以及為何在日常工作中不推薦使用+號進行字符串拼接。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,733評論 18 399
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,818評論 18 139
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,189評論 2 7
  • 個人筆記,方便自己查閱使用 Contents Java LangAssignment, ReferenceData...
    freenik閱讀 1,400評論 0 6
  • 大多數人在過一種舍本逐末的生活,為了賺取更多的錢,犧牲健康、快樂、家庭,視線永遠停留在未來,希望通過壓榨今天,為明...
    3分鐘心理學閱讀 326評論 0 1