java基礎解析系列(九)---String不可變性分析
目錄
- java基礎解析系列(一)---String、StringBuffer、StringBuilder
- java基礎解析系列(二)---Integer緩存及裝箱拆箱
- java基礎解析系列(三)---HashMap原理
- java基礎解析系列(四)---LinkedHashMap的原理及LRU算法的實現
- java基礎解析系列(五)---HashMap並發下的問題以及HashTable和CurrentHashMap的區別
- java基礎解析系列(六)---注解原理及使用
- java基礎解析系列(七)---ThreadLocal原理分析
- java基礎解析系列(八)--fail-fast機制及CopyOnWriteArrayList的原理
- 這是我的博客目錄,歡迎閱讀
什么是不可變
- 一個對象,在它創建完成之后,不能再改變它的狀態,那么這個對象就是不可變的。不能改變狀態的意思是,不能改變對象內的成員變量,包括基本數據類型的值不能改變,引用類型的變量不能指向其他的對象,引用類型指向的對象的狀態也不能改變
先看一個例子
public static void main(String[] args) throws Exception {
String s=new String("jia");
String s2=s.concat("jun");
System.out.println(s);
StringBuffer sb=new StringBuffer("jia");
sb.append("jun");
System.out.println(sb);
}
輸出jia和jiajun
- 對字符串s的操作並沒有改變他,而對StringBuffer sb進行apped,輸出的時候卻改變了,這就說明了String一個不可變性。
也許你會說這是可變的
public static void main(String[] args) {
String s1="jiajun";
String s2=s1;
s1="666";
System.out.println(s1);
}
輸出:666
- 實際上,"jiajun"字符串並沒有改變,可以通過一個例子來證明
String s3="jiajun";
System.out.println(s2==s3);
輸出:true
- 為什么會這樣,因為實際上"jiajun"字符串存放在了常量池,此時s2和s3都指向了這個這個字符串,所以可以證明這個字符串是不改變的並存在的
- 之所以會輸出666,是因為此時s1指向的字符串是另一個了
- 其實最本質的是這個改變是改變s1的引用
也許你會說這是可變的
public static void main(String[] args) {
String s1="jiajun";
s1=s1.replace("j","J");
System.out.println(s1);
s1=s1.toLowerCase();
System.out.println(s1);
}
JiaJun
jiajun
- 實際上jiajun字符串還是沒有改變的,看一下方法的源碼
2047 public String More ...replace(char oldChar, char newChar) {
2048 if (oldChar != newChar) {
...
2069 return new String(0, len, buf);
2070 }
2071 }
2072 return this;
2073 }
- 可以看到返回的時候是創建一個新的字符串
- 實際上String的一些方法substring, replace, replaceAll, toLowerCase,返回的時候是創建一個新的String
分析源碼
111 public final class String
112 implements java.io.Serializable, Comparable<String>, CharSequence {
The value is used for character storage.
113
114 private final char value[];
Cache the hash code for the string
116
117 private int hash; // Default to 0
118
private static final long serialVersionUID = -6849794470754667710L;
136
137 public String() {
138 this.value = new char[0];
139 }
151 public String(String original) {
152 this.value = original.value;
153 this.hash = original.hash;
154 }
1913 public String substring(int beginIndex) {
1914 if (beginIndex < 0) {
1915 throw new StringIndexOutOfBoundsException(beginIndex);
1916 }
1917 int subLen = value.length - beginIndex;
1918 if (subLen < 0) {
1919 throw new StringIndexOutOfBoundsException(subLen);
1920 }
1921 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
1922 }
- 111行可以看到,String類是用final修飾的,說明這個類是無法被繼承的
- 114行可以String類里面維護一個value的char數組,這個數組是用final修飾的,說明這個value不能指向別的數組,但是並不說明這個value數組的內容不可變,而這個value是用private修飾的,說明只有在類里面可以修改訪問他,在外部不能改變他,這是關鍵
- 從1913行可以看到substring方法實際上返回的數組是新創建的數組
怎么實現不可變
- String里面維護的value數組是用private final修飾的,無法改變引用,也無法訪問這個數組修改數組的值,最關鍵的是private
- 對Sting的操作,並沒有修改數組的值,而是創建新的String
- 類用final修飾,方法無法被子類重寫,避免被其他人破壞
不可變的好處
- 節省空間,大量使用相同的字符串,同時指向常量池的字符串就行,如果字符串是可變的話,那么常量池就沒意義了
String s1="jiajun";
String s2="jiajun";
System.out.println(s1==s2);
-
線程安全,出現線程安全的是在對共享變量寫的時候,而因為不可變,所以Strig是線程安全的
-
最重要的是安全,如果當一個String已經傳給別人了,這個時候如果是可變,那么可以在后面進行修改,那么這是麻煩並不安全的。而且在hashmap中,如果作為key的String s1是可變的,那么這樣是很危險的,比如說可能出現兩個同樣的鍵。
真的不可變嗎
public static void main(String[] args) throws Exception {
String s1="jiajun";
Field field=String.class.getDeclaredField("value");
field.setAccessible(true);
char [] value=(char[])field.get(s1);
value[0]='Jiajun';
- 實際上,通過反射可以修改value數組
為什么設置為不可變
- 調用其他方法,比如調用一些系統級操作之前,可能會有一系列校驗,如果是可變類的話,可能在你校驗過后,其內部的值被改變了,可能引起嚴重的系統崩潰問題
- 當你在傳參的時候,使用不可變類不需要去考慮誰可能會修改其內部的值
我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)
作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。