@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" ;
字符串拼接的全過程如下:
- 臨時創(chuàng)建一個StringBuffer 對象淘钟,并調(diào)用其append(String str) 方法進(jìn)行字符串的連接操作
- 調(diào)用StringBuffer 對象的toString() 方法轉(zhuǎn)換成String 對象,其內(nèi)容為“ab"
- 將生成的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)步笋鄙。