上一節咱們講解到字符串本質上就是字符數組,同時詳細講解了字符串判斷相等須要注意的地方,本節咱們來深刻探討字符串特性,下面咱們一塊兒來看看。java
咱們依然藉助初始化字符串的方式來探討字符串的不可變性,以下:數組
String str = "Jeffcky";
System.out.println(str);
上述咱們經過字面量的方式來建立字符串,接下來咱們對字符串str進行以下操做:緩存
String str = "Jeffcky";
str.substring(0,3).concat("wang").toLowerCase().trim();
System.out.println(str);
咱們看到針對str字符串進行截取、鏈接、小寫等操做後,字符串的值依然未發生改變,這就是字符串的不可變性,咱們經過查看任意一個對字符串操做的方法,好比concat方法源碼:安全
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
經過查看concat源碼得出:原始的str值永遠都沒有發生改變,它的值只是被複制,而後將咱們鏈接的文本添加到複製的副本里,最後返回一個新的String。因此到這裏咱們知道針對字符串的操做都是複製一份字符串,而後對複製後的字符串進行操做,最終返回一個新的String對象。多線程
咱們再來看看上一節所給出的代碼示例,以下:優化
public class Main { public static void main(String[] args) { String str1 = "Jeffcky"; String str2 = "Jeffcky"; System.out.println(str1 == str2); System.out.println(str1.equals(str2)); } }
當咱們實例化一個String時(在本例中爲Jeffcky)保存在Java堆內存(用於全部Java對象的動態內存分配)中。雖然在這個例子中咱們有兩個不一樣的引用變量,但它們都只是指Java Heap Memory中的同一內存位置,雖然看起來有兩個不一樣的String對象,但實際上只有一個,而str2永遠不會被實例化爲對象,而是在內存中分配對應於str1的對象,這是由於Java針對字符串進行了優化處理,每次要實例化此類String對象時,都會將要添加到堆內存的值與先前添加的值進行比較,若是值已存在,則不初始化對象,並將值分配給引用變量,這些值保存在名叫「字符串池」中,該字符串池包含全部文字字符串值,固然咱們能夠經過new運算符繞過這種狀況。this
上述咱們經過例子說明了字符串的不可變性特性,那麼爲何字符串是不可變的呢?能夠參考知乎回答:《https://www.zhihu.com/question/31345592》。我認爲主要在於有效共享對象,節省內存空間。當程序運行時,建立的String實例的數量也會增加,若是不緩存String常量,堆空間中會有大量的String,佔用內存空間,因此String對象被建立後緩存在字符串池中,若緩存的字符串被多個客戶端共享,此時一個客戶端的操做修改了字符串則影響到其餘客戶端,所以經過字符串的不可變性來規避這種風險,同時經過緩存和共享字符串常量,JVM爲Java應用程序節省內存。spa
有了字符串不可變性,能夠很安全的被多線程所共享,咱們不用擔憂線程同步問題,確保線程安全。線程
有了字符串不可變性,能夠很好的使用好比HashMap,咱們能正確檢索到存儲到HashMap中的對象,若字符串可變且在插入到HashMap後並修改了字符串內容,此時將會出現丟失對應字符串所映射的對象。設計
有了字符串不可變性,此時會緩存字符串哈希碼,因此每次調用字符串的hashcode方法時都不用計算,使得在HashMap中使用鍵很是快。
其餘等等......
接下來咱們一塊兒來經過源碼的方式來看看String的實現,以下:
public class Main { public static void main(String[] args) { char a[] = {'j', 'e', 'f', 'f', 'c', 'k', 'y'}; String str = new String(a); System.out.println(str); } }
咱們經過字符數組建立字符串的方式去查看源碼,以下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** * Initializes a newly created {@code String} object so that it represents * an empty character sequence. Note that use of this constructor is * unnecessary since Strings are immutable. */ public String() { this.value = new char[0]; } /** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } ... }
咱們看到字符串對象定義爲final(當前咱們還未學到final,咱們只須要知道經過final關鍵字修飾說明該類不可繼承),網上有不少例子說字符串對象經過final關鍵字修飾,說明字符串不可變,其實這種說法是不嚴謹且錯誤的。String經過final關鍵字修飾的緣由在於:確保不能經過擴展和覆蓋行爲來破壞String類的不可變性而非說明字符串不可變。好比,以下例子:
public class Main { public static void main(String[] args) { String str1 = "Jeffcky"; String str2 = "Jeffcky".toUpperCase(); System.out.println(str1); System.out.println(str2); } }
如今字符串str2爲"Jeffcky".toUpperCase(),咱們將同一個對象修改成「JEFFCKY」,若是修改了字符串變量,其餘字符串變量也將自動受到影響,好比str1也將是"JEFFCKY",很顯然是不可取的。
本文咱們詳細介紹了字符串的不可變、字符串池特性,同時解釋了字符串爲什麼不可變,以及說明字符串類定義爲final,並非說明其不可變,只是爲了避免容許經過擴展或覆蓋來破壞字符串的不可變性。