如何給女朋友解釋為什么Java里面的String對象是不可變的?








String的不變性
String在Java中特別常用,相信很多人都看過他的源碼,在JDK中,關于String的類聲明是這樣的:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
}
可以看到,String類是final類型的,那么也就是說,String是一個不可變對象。
不可變對象是在完全創(chuàng)建后其內(nèi)部狀態(tài)保持不變的對象。這意味著,一旦對象被賦值給變量,我們既不能更新引用,也不能通過任何方式改變內(nèi)部狀態(tài)。
可是有人會有疑惑,String為什么不可變,我的代碼中經(jīng)常改變String的值啊,如下:
String s = "abcd";
s = s.concat("ef");
這樣,操作,不就將原本的"abcd"的字符串改變成"abcdef"了么?
但是,雖然字符串內(nèi)容看上去從"abcd"變成了"abcdef",但是實際上,我們得到的已經(jīng)是一個新的字符串了。

如上圖,在堆中重新創(chuàng)建了一個"abcdef"字符串,和"abcd"并不是同一個對象。
所以,一旦一個string對象在內(nèi)存(堆)中被創(chuàng)建出來,他就無法被修改。而且,String類的所有方法都沒有改變字符串本身的值,都是返回了一個新的對象。
如果我們想要一個可修改的字符串,可以選擇StringBuffer 或者 StringBuilder這兩個代替String。



為什么String要設計成不可變
在知道了"String是不可變"的之后,大家是不是一定都很疑惑:為什么要把String設計成不可變的呢?有什么好處呢?
這個問題,困擾過很多人,甚至有人直接問過Java的創(chuàng)始人James Gosling。
在一次采訪中James Gosling被問到什么時候應該使用不可變變量,他給出的回答是:
I would use an immutable whenever I can.
那么,他給出這個答案背后的原因是什么呢?是基于哪些思考的呢?
其實,主要是從緩存、安全性、線程安全和性能等角度觸發(fā)的。


緩存
字符串是使用最廣泛的數(shù)據(jù)結構。大量的字符串的創(chuàng)建是非常耗費資源的,所以,Java提供了對字符串的緩存功能,可以大大的節(jié)省堆空間。
JVM中專門開辟了一部分空間來存儲Java字符串,那就是字符串池。
通過字符串池,兩個內(nèi)容相同的字符串變量,可以從池中指向同一個字符串對象,從而節(jié)省了關鍵的內(nèi)存資源。
String s = "abcd";
String s2 = s;
對于這個例子,s和s2都表示"abcd",所以他們會指向字符串池中的同一個字符串對象:

但是,之所以可以這么做,主要是因為字符串的不變性。試想一下,如果字符串是可變的,我們一旦修改了s的內(nèi)容,那必然導致s2的內(nèi)容也被動的改變了,這顯然不是我們想看到的。
安全性
字符串在Java應用程序中廣泛用于存儲敏感信息,如用戶名、密碼、連接url、網(wǎng)絡連接等。JVM類加載器在加載類的時也廣泛地使用它。
因此,保護String類對于提升整個應用程序的安全性至關重要。
當我們在程序中傳遞一個字符串的時候,如果這個字符串的內(nèi)容是不可變的,那么我們就可以相信這個字符串中的內(nèi)容。
但是,如果是可變的,那么這個字符串內(nèi)容就可能隨時都被修改。那么這個字符串內(nèi)容就完全可信了。這樣整個系統(tǒng)就沒有安全性可言了。
線程安全
不可變會自動使字符串成為線程安全的,因為當從多個線程訪問它們時,它們不會被更改。
因此,一般來說,不可變對象可以在同時運行的多個線程之間共享。它們也是線程安全的,因為如果線程更改了值,那么將在字符串池中創(chuàng)建一個新的字符串,而不是修改相同的值。因此,字符串對于多線程來說是安全的。
hashcode緩存
由于字符串對象被廣泛地用作數(shù)據(jù)結構,它們也被廣泛地用于哈希實現(xiàn),如HashMap、HashTable、HashSet等。在對這些散列實現(xiàn)進行操作時,經(jīng)常調(diào)用hashCode()方法。
不可變性保證了字符串的值不會改變。因此,hashCode()方法在String類中被重寫,以方便緩存,這樣在第一次hashCode()調(diào)用期間計算和緩存散列,并從那時起返回相同的值。
在String類中,有以下代碼:
private int hash;//this is used to cache hash code.
性能
前面提到了的字符串池、hashcode緩存等,都是提升性能的提現(xiàn)。
因為字符串不可變,所以可以用字符串池緩存,可以大大節(jié)省堆內(nèi)存。而且還可以提前對hashcode進行緩存,更加高效
由于字符串是應用最廣泛的數(shù)據(jù)結構,提高字符串的性能對提高整個應用程序的總體性能有相當大的影響。
總結
通過本文,我們可以得出這樣的結論:字符串是不可變的,因此它們的引用可以被視為普通變量,可以在方法之間和線程之間傳遞它們,而不必擔心它所指向的實際字符串對象是否會改變。
我們還了解了促使Java語言設計人員將該類設置為不可變類的其他原因。主要考慮的是緩存、安全性、線程安全和性能等方面






有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
