在JDK API的對String的描述中,有以下對String的介紹:
String 類代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作為此類的實例實現(xiàn)。</br>
字符串是常量;它們的值在創(chuàng)建之后不能更改飒箭。字符串緩沖區(qū)支持可變的字符串。因為 String 對象是不可變的蜒灰,所以可以共享弦蹂。
在這里指出了String對象時不可變的,那么下面我們來具體分析為什么java中的String是不可變的强窖。
不可變對象
不可變對象是指一個對象的狀態(tài)在對象被創(chuàng)建之后就不再變化凸椿。不可改變的意思就是說:不能改變對象內(nèi)的成員變量,包括基本數(shù)據(jù)類型的值不能改變翅溺,引用類型的變量不能指向其他的對象脑漫,引用類型指向的對象的狀態(tài)也不能改變髓抑。
那么會有人說,以下的代碼不就改變了String嗎:
public static void main(String[] args) {
String s = "ABCDEF";
System.out.println("s = " + s);
s = "123456";
System.out.println("s = " + s);
}
打印的結(jié)果:</br>
s = ABCDEF</br>
s = 123456
- 你看窿撬,從結(jié)果上看启昧,確實是如此叙凡,s的值改變了劈伴,這不是和我們說的不可變對象String完全不搭邊。
- 其實握爷,這里存在一個誤區(qū)跛璧,s呢 ,僅僅是一個String對象的引用新啼,并不是對象本身追城。對象在內(nèi)存中是一塊內(nèi)存區(qū),而s只是一個引用燥撞,它指向了一個具體的對象座柱。
- 在創(chuàng)建String對象的時候,s指向的是內(nèi)存中的"ABDCEF",當(dāng)執(zhí)行語句
s = "123456"
后物舒,其實又創(chuàng)建了一個新的對象"123456",而s重新指向了這個新的對象色洞,同時原來的"ABCDEF"并沒有發(fā)生改變,仍保存在內(nèi)存中冠胯。
在這里火诸,對象引用和C語言中的指針其實很像,它們都是存放的對象在內(nèi)存中的地址值荠察,只是在java中的引用喪失了部分靈活性置蜀,比如就不能像C中的指針那樣進(jìn)行運算。
那么又會有朋友說啦悉盆,String類中有修改自身的方法又是怎樣呢盯荤,比如不是有一個replaced(替換)方法么
public static void main(String[] args) {
String s = "ABCDEF";
System.out.println("s = " + s);
s.replace("A", "a");
System.out.println("s = " + s);
}
打印結(jié)果:</br>
s = ABCDEF</br>
s = ABCDEF</br>
結(jié)果是不是沒有改變,是否出乎你的意料呢焕盟,現(xiàn)在改一下代碼:
public static void main(String[] args) {
String s = "ABCDEF";
System.out.println("s = " + s);
s = s.replace("A", "a");
System.out.println("s = " + s);
}
打印結(jié)果:</br>
s = ABCDEF</br>
s = aBCDEF</br>
這時候你會發(fā)現(xiàn)秋秤,結(jié)果確實是替換了,但是并不是說String對象發(fā)生了改變京髓,而是執(zhí)行語句s = s.replace("A", "a")
將引用s指向了一個新的String對象("aBCDEF")航缀,原來的"ABCDEF"還在內(nèi)存并沒有被改變。
如何實現(xiàn)不可變
要了解String的不可變性堰怨,首先看一下String類中都有哪些成員變量芥玉,
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
- 從注釋中,可以看出value變量是String用來存放字符的备图,還有一個hash成員變量是該String對象的哈希值緩存灿巧。
- value赶袄,hash這兩個成員變量都是private final修飾的,而String類中并沒有提供set/get等公共方法來修改這些值抠藕,所以String類的外部無法修改String饿肺,同時final保證了在String內(nèi)部,只要值初始化了盾似,也不能被改變敬辣。
- 所以認(rèn)為String對象是不可變的。
- 而在java中零院,數(shù)據(jù)也是對象溉跃,那么value也僅僅是一個引用,指向真正的數(shù)組對象告抄,這將在下面的文章中用到撰茎。
不可變對象的好處
- 不可變對象更容易構(gòu)造,測試與使用打洼;
- 真正不可變對象都是線程安全的龄糊;
- 不可變對象的使用沒有副作用(沒有保護(hù)性拷貝);
- 對象變化的問題得到了避免募疮;
- 不可變對象的失敗都是原子性的炫惩;
- 不可變對象更容易緩存,且可以避免null引用酝锅;
- 不可變對象可以避免時間上的耦合诡必;
String,StringBuffer搔扁,StringBuilder爸舒,都是final類,不允許被繼承稿蹲,在本質(zhì)上都是字符數(shù)組扭勉,不同的是,String的長度是不可變的而后兩者長度可變苛聘,在進(jìn)行連接操作時涂炎,String每次返回一個新的String實例,而StringBuffer和StringBuilder的append方法直接返回this设哗,所以當(dāng)進(jìn)行大量的字符串連接操作時唱捣,不推薦使用String,因為它會產(chǎn)生大量的中間String對象网梢。
StringBuffer和StringBuilder的一個區(qū)別是震缭,StringBuffer在append方法前增加了一個synchronized修飾符,以起到同步的作用战虏,為此也降低了執(zhí)行效率拣宰;若要在toString方法中使用循環(huán)党涕,使用StringBuilder
那么,真的不可變么
現(xiàn)在我們已經(jīng)知道了String的成員變量是private final 的巡社,也就是初始化之后不可改變的膛堤。同時也提到value這個成員變量其實也是一個引用,指向真正的數(shù)組內(nèi)存地址晌该,不能改變它的引用指向肥荔,我們能不能直接改變內(nèi)存數(shù)組中的數(shù)據(jù)呢,那么就需要獲取到value气笙,而value是私有的次企,我們怎么獲取呢?對潜圃!就是用反射。
public static void reflectString() throws Exception{
String s = "ABCDEF";
System.out.println("s = " + s);
Field valueField = s.getClass().getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[0] = 'a';
value[2] = 'c';
value[4] = 'e';
System.out.println("s = " + s);
}
打印結(jié)果:</br>
s = ABCDEF</br>
s = aBcDeF</br>
結(jié)果顯而易見舟茶!