1. 什麼是不可變?
String不可變很簡單,如下圖,給一個已有字元串"abcd"第二次指派成"abcedl",不是在原記憶體位址上修改資料,而是重新指向一個新對象,新位址。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiMGc902byZ2PzMmM0EjN2YDO0UGOykjNhVmM4QzYyAjZ1MWZ2MDN2Q2LcBza5QTcsJja2FXLp1ibj1ycvR3Lc5Wanlmcv9CXt92YucWbp9WYpRXdvRnL5A3Lc9CX6MHc0RHaiojIsJye.jpg)
2. String為什麼不可變?
翻開JDK源碼,java.lang.String類起手前三行,是這樣寫的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** String本質是個char數組. 而且用final關鍵字修飾.*/
private final char value[];
private int hash;
...}
首先String類是用final關鍵字修飾,這說明String不可繼承。再看下面,String類的主力成員字段value是個char[ ]數組,而且是用final修飾的。final修飾的字段建立以後就不可改變。
有的人以為故事就這樣完了,其實沒有。因為雖然value是不可變,也隻是value這個引用位址不可變。擋不住Array數組是可變的事實。Array的資料結構看下圖
也就是說Array變量隻是stack上的一個引用,數組的本體結構在heap堆。String類裡的value用final修飾,隻是說stack裡的這個叫value的引用位址不可變。沒有說堆裡array本身資料不可變。看下面這個例子,
final int[] value={1,2,3}int[] another={4,5,6};value=another; //編譯器報錯,final不可變
value用final修飾,編譯器不允許我把value指向堆區另一個位址。但如果我直接對數組元素動手,分分鐘搞定。
final int[] value={1,2,3};value[2]=100; //這時候數組裡已經是{1,2,100}
或者更粗暴的反射直接改,也是可以的。
final int[] array={1,2,3};Array.set(array,2,100); //數組也被改成{1,2,100}
是以String是不可變,關鍵是因為SUN公司的工程師,在後面所有String的方法裡很小心的沒有去動Array裡的元素,沒有暴露内部成員字段。
private final char value[]這一句裡,private的私有通路權限的作用都比final大。而且設計師還很小心地把整個String設成final禁止繼承,避免被其他人繼承後破壞。是以String是不可變的關鍵都在底層的實作,而不是一個final。考驗的是工程師構造資料類型,封裝資料的功力。
3.不可變有什麼好處?
為了安全。
class Test{
public static String appendStr(String s){
s +="bbb";
return s;
}
public static StringBuilder appendStr(StringBuilder sb){
return sb.append("bbb");
}
public static void main(String[] args) {
String s = new String("aaa");
String ns = Test.appendStr(s);
System.out.println("string aaa>>> " + s.toString());
StringBuilder sb = new StringBuilder("aaa");
StringBuilder nsb = Test.appendStr(sb);
System.out.println("stringBuilder aaa >>>" +sb.toString());
}
}
//輸出如下
//string aaa>>> aaa
// stringbuilder aaa >>> aaabbb
如果程式員不小心像上面例子裡,直接在傳進來的參數上加"bbb",因為Java對象參數傳的是引用,是以可變的的StringBuilder參 數就被改變了。可以看到變量sb在Test.appendSb(sb)操作之後,就變成了"aaabb"。有的時候這可能不是程式員的本意。是以String不可變的安全性就展現在這裡。
還有一個大家都知道,就是在并發場景下,多個線程同時讀一個資源, 不會引發竟态條件的。隻有對資源做寫操作才有危險。可對象不能被寫,是以線程安全。後别忘了String另外一個字元串常量池的屬性。像下面這樣字元串one和two都用字面量"something"指派。它們其實都指向同一個記憶體位址。
String one = "someString";String two = "someString";
這樣在大量使用字元串的情況下,可以節省記憶體空間,提高效率。但之是以能實作這個特性,String的不可變性是最基本的一個必要條件。要是記憶體裡字元串内容能改來改去,這麼做就完全沒有意義了。
連結:https://www.zhihu.com/question/31345592/answer/2178932765