Java 中String 類的不可變性與字符串拼接解析

@TOC

一穴翩、String 類是不可變的

1.1 不可變的原因

在Java 中,對于String 類的定義如下:


在這里插入圖片描述

由圖可知,String 類的值存儲于其私有變量value 中,而變量 value 是final 修飾的铅歼。
而在Java 中,final 修飾引用變量時代表給該引用無法修改其對象但可以改變其狀態(tài)换可。
同時經(jīng)過閱讀源碼椎椰,我們發(fā)現(xiàn),在String 類中 value 是私有變量且沒有提供對應(yīng)的setter 方法沾鳄;除了構(gòu)造方法外慨飘,類中的方法也不會觸碰value 中的元素。
以substring(int beginIndex,int endIndex) 方法為例译荞,該方法并不會去修改當(dāng)前String 對象的任何變量瓤的,而是使用 new String(...) 直接創(chuàng)建一個新的String 對象或返回自身。
也就是說吞歼,對于String 對象圈膏,一旦我們對其進(jìn)行了賦值,該對象中的value 變量也就不再會有變化了篙骡。
這就是為什么我們說String 類是不可變類的原因稽坤。

1.2 不可變的好處

String 類是不可變類,其對象已經(jīng)創(chuàng)建就不能進(jìn)行修改糯俗。因此尿褪,在多線程操作時,可以認(rèn)為其是不變的得湘,不用擔(dān)心其他線程有意或無意間對其進(jìn)行了修改杖玲。
String 類得不可變性使其性質(zhì)類似于只讀得共享文件,不用擔(dān)心因并發(fā)操作而導(dǎo)致的一系列問題淘正,也就沒必要使用線程同步操作摆马,簡單方便。

二鸿吆、字符串的"+" 拼接

2.1 官方解釋

oracle jdk 8 的官方文檔中關(guān)于Sting 類的描述中有這么一段文字:

The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. String concatenation is implemented through the StringBuilder(or StringBuffer) class and its append method.

具體的翻譯是:

Java 語言提供對字符串串聯(lián)符號( " + " )以及將其他對象轉(zhuǎn)換為字符串的特殊支持今膊。字符串串聯(lián)是通過 StringBuilder(或StringBuffer )類及其 append 方法實(shí)現(xiàn)的。

2.2 append() 方法

通過查看StringBuffer 類的源碼伞剑,可以得知:對于append(...) 方法斑唬,在StringBuffer 類中并沒有進(jìn)行覆寫,而是直接調(diào)用其父類的方法來實(shí)現(xiàn)黎泣。


在這里插入圖片描述

而StringBuffer 類的父類是AbstractStringBuilder 抽象類恕刘。

public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence

2.3 具體實(shí)現(xiàn)

在AbstractStringBuilder 抽象類中對于append(...) 方法的定義有很多,我們主要看參數(shù)為String 類型的方法抒倚,其他的均是類似的過程:

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
}

private AbstractStringBuilder appendNull() {
        int c = count;
        ensureCapacityInternal(c + 4);
        final char[] value = this.value;
        value[c++] = 'n';
        value[c++] = 'u';
        value[c++] = 'l';
        value[c++] = 'l';
        count = c;
        return this;
    }

String 中對于getChars(...) 方法的定義:

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
        if (srcBegin < 0) {
            throw new StringIndexOutOfBoundsException(srcBegin);
        }
        if (srcEnd > value.length) {
            throw new StringIndexOutOfBoundsException(srcEnd);
        }
        if (srcBegin > srcEnd) {
            throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
        }
        System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
    }

對于以上兩段源碼褐着,其中,

  • ensureCapacityInternal(...) 方法的主要作用是擴(kuò)充value 數(shù)組的大小托呕,其實(shí)現(xiàn)最后還是使用System.arraycopy(...) 方法實(shí)現(xiàn)對原數(shù)組的復(fù)制含蓉。
  • System.arraycopy(...) 方法的作用就是實(shí)現(xiàn)數(shù)組之間的復(fù)制频敛。

2.4 源碼解析

2.4.1 方法解析

對于System.arraycopy(...) 方法:

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

參數(shù)解釋:

  • Object src : 源數(shù)組
  • int srcPos : 源數(shù)組的起始位置
  • Object dest : 目標(biāo)數(shù)組
  • int destPos : 目標(biāo)數(shù)組的起始位置
  • int length : 復(fù)制長度

實(shí)現(xiàn)功能:

將參數(shù)str 從下標(biāo)為srcPos 的位置開始,總共length 個字符( 在實(shí)際中不一定是str 的總長度 ) 復(fù)制到變量dest 中從下標(biāo)為destPos 開始的位置馅扣。

2.4.2 實(shí)際調(diào)用

在append(String str) 方法調(diào)用過程中斟赚,對于最后的System.arraycopy(...) 方法,其參數(shù)具體如下:

 System.arraycopy(String.value, 0, AbstractStringBuilder.value,  AbstractStringBuilder.count, str.length());

其中差油,

  • String.value實(shí)際上就是append(String str) 方法的參數(shù)str 拗军;
  • 0 表示從str 下標(biāo)為0 的位置開始復(fù)制;
  • AbstractStringBuilder.value 實(shí)際上就是StringBuffer 的字符數(shù)組蓄喇,也就是說str 字符串最后添加到了StringBuffer 的字符數(shù)組后面发侵;
  • AbstractStringBuilder.count 是AbstractStringBuilder 類中字符數(shù)組value 的長度( 即StringBuffer 的字符數(shù)組的長度 ),表示第一個復(fù)制字符的存儲位置是value[count] 妆偏;
  • str.length() 顧名思義刃鳄,指參數(shù)str 的長度,也就是說钱骂,此次復(fù)制是復(fù)制整個str 字符串铲汪。

也就是說,在實(shí)際的運(yùn)行過程中罐柳,調(diào)用的System.arraycopy(...) 方法是這樣子的:

System.arraycopy(str, 0, value, count, len);

因?yàn)閏ount= value.length 并且 len=str.length掌腰,所以該方法實(shí)現(xiàn)的功能是:
將字符串str 拼接到字符數(shù)組value 之后,這里的value 指的是AbstractStringBuilder 類中的字符數(shù)組value 张吉,也是StringBuffer 的字符數(shù)組齿梁。

2.5 轉(zhuǎn)換成String

經(jīng)過前面的幾步,字符串str 已經(jīng)成功拼接到了StringBuffer 的字符數(shù)組之后了肮蛹,但是StringBuffer 又是怎么轉(zhuǎn)換成String 類的呢勺择?
其實(shí)很簡單,只需要調(diào)用StringBuffer 的toString() 方法即可伦忠。
StringBuffer 類中對于toString() 方法的定義如下:

 @Override
public synchronized String toString() {
    if (toStringCache == null) {
        toStringCache = Arrays.copyOfRange(value, 0, count);
    }
    return new String(toStringCache, true);
}

String 類的構(gòu)造方法:

String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

由上述源碼可以發(fā)現(xiàn)省核,當(dāng)調(diào)用StringBuffer 類的toString() 方法時,會自動新建一個String 對象昆码,用以存儲StringBuffer 類中字符數(shù)組的值气忠。

三、總結(jié)

3.1 String 類的不可變性

String 類是不可變類的原因:

  • String 類的值封裝在字符數(shù)組value 中赋咽,而value 是被final 修飾的旧噪,不可以改變對象
  • 字符數(shù)組value 是私有變量,且沒有提供setter 方法
  • 除了構(gòu)造方法脓匿,在String 類中的方法里都沒有觸碰字符數(shù)組value 里的元素

3.2 使用"+" 進(jìn)行字符串拼接的過程

對于代碼

String str = new String("a") ;
String string = str + "b" ;

字符串拼接的全過程如下:

  1. 臨時創(chuàng)建一個StringBuffer 對象淘钟,并調(diào)用其append(String str) 方法進(jìn)行字符串的連接操作
  2. 調(diào)用StringBuffer 對象的toString() 方法轉(zhuǎn)換成String 對象,其內(nèi)容為“ab"
  3. 將生成的String 對象賦值給變量string

這里要注意一點(diǎn):

當(dāng)使用運(yùn)算符"+" 連接字符串時陪毡,如果兩個操作數(shù)都是編譯時常量米母,則在編譯期就會計(jì)算出該字符串的值勾扭,而不會在運(yùn)行時創(chuàng)建StringBuffer 或 StringBuilder 對象。

在實(shí)際編碼時铁瞒,我們常提倡不使用"+" 反復(fù)進(jìn)行String 對象的連接妙色,而是直接使用StringBuffer 或 StringBuilder 來連接的原因就是可以省去每次系統(tǒng)創(chuàng)建StringBuffer 或 StringBuilder的開銷。

至此精拟,本文結(jié)束。我是陳冰安虱歪,一個Java學(xué)習(xí)者蜂绎。歡迎關(guān)注我的公眾號【暗星涌動】,愿與你一同進(jìn)步笋鄙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末师枣,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萧落,更是在濱河造成了極大的恐慌践美,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件找岖,死亡現(xiàn)場離奇詭異陨倡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)许布,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門兴革,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜜唾,你說我怎么就攤上這事杂曲。” “怎么了袁余?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵擎勘,是天一觀的道長。 經(jīng)常有香客問我颖榜,道長棚饵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任掩完,我火速辦了婚禮蟹地,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藤为。我一直安慰自己怪与,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布缅疟。 她就那樣靜靜地躺著分别,像睡著了一般遍愿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耘斩,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天沼填,我揣著相機(jī)與錄音,去河邊找鬼括授。 笑死坞笙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荚虚。 我是一名探鬼主播薛夜,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼版述!你這毒婦竟也來了梯澜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤渴析,失蹤者是張志新(化名)和其女友劉穎晚伙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俭茧,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咆疗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了母债。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片民傻。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖场斑,靈堂內(nèi)的尸體忽然破棺而出漓踢,到底是詐尸還是另有隱情,我是刑警寧澤漏隐,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布喧半,位于F島的核電站,受9級特大地震影響青责,放射性物質(zhì)發(fā)生泄漏挺据。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一脖隶、第九天 我趴在偏房一處隱蔽的房頂上張望扁耐。 院中可真熱鬧,春花似錦产阱、人聲如沸婉称。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽王暗。三九已至悔据,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俗壹,已是汗流浹背科汗。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绷雏,地道東北人头滔。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像涎显,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子棺禾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350