String:String類型爲何不可變

在學習Java的過程當中,咱們會被告知 String 被設計成不可變的類型。爲何 String 會被 Java 開發者有如此特殊的對待?他們的設計意圖和設計理念究竟是什麼?所以,我帶着如下三個問題,對
String 進行剖析:java

  • String 真的不可變嗎?數據庫

  • 爲何會將 String 設計爲不可變?編程

  • 如何經過技術實現實現 String 不可變 ?數組

String 真的不可變?

String 底層實現:緩存

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    
    //other codes
}

String 的底層實現是依靠 char[] 數組,既然依靠的是基礎類型變量,那麼他必定是可變的, String 之因此不可變,是由於 Java 的開發者經過技術實現,隔絕了使用者對 String 的底層數據的操做。可是,咱們能夠同反射的機制,操做 String 的底層,檢驗其不可變的猜測。安全

反射的方式操做 String :併發

//建立字符串"Hello World", 並賦給引用s  
        String s = "Hello World";   
          
        System.out.println("s = " + s);    // Hello World  
          
        //獲取String類中的value字段  
        Field valueFieldOfString = String.class.getDeclaredField("value");  
          
        //改變value屬性的訪問權限  
        valueFieldOfString.setAccessible(true);  
          
        //獲取s對象上的value屬性的值  
        char[] value = (char[]) valueFieldOfString.get(s);  
          
        //改變value所引用的數組中的第5個字符  
        value[5] = '_';  
          
        System.out.println("s = " + s);    //Hello_World

經過兩次字符串的輸出,咱們能夠看到,String 被改變了,可是在代碼裏,幾乎不會使用反射的機制去操做 String 字符串,因此,咱們會認爲 String 類型是不可變的。app


爲何會將 String 設計爲不可變

  • 安全socket

    • 引起安全問題,譬如,數據庫的用戶名、密碼都是以字符串的形式傳入來得到數據庫的鏈接,或者在socket編程中,主機名和端口都是以字符串的形式傳入。由於字符串是不可變的,因此它的值是不可改變的,不然黑客們能夠鑽到空子,改變字符串指向的對象的值,形成安全漏洞性能

    • 保證線程安全,在併發場景下,多個線程同時讀寫資源時,會引競態條件,因爲 String 是不可變的,不會引起線程的問題而保證了線程

    • HashCode,當 String 被建立出來的時候,hashcode也會隨之被緩存,hashcode的計算與value有關,若 String 可變,那麼 hashcode 也會隨之變化,針對於 Map、Set 等容器,他們的鍵值須要保證惟一性和一致性,所以,String 的不可變性使其比其餘對象更適合當容器的鍵值。

  • 性能

    • 當字符串是不可變時,字符串常量池纔有意義。字符串常量池的出現,能夠減小建立相同字面量的字符串,讓不一樣的引用指向池中同一個字符串,爲運行時節約不少的堆內存。若字符串可變,字符串常量池失去意義,基於常量池的String.intern()方法也失效,每次建立新的 String 將在堆內開闢出新的空間,佔據更多的內存

實例代碼:

String 的不可變性:

public static String appendStr(String s){
        s+="bbb";
        return s;
    }

    //可變的StringBuilder
    public static StringBuilder appendSb(StringBuilder sb){
        return sb.append("bbb");
    }

    public static void main(String[] args){
        //String作參數
        String s=new String("aaa");
        String ns=Test.appendStr(s);
        System.out.println("String aaa >>> "+s.toString()); // aaa

        //StringBuilder作參數
        StringBuilder sb=new StringBuilder("aaa");
        StringBuilder nsb=Test.appendSb(sb);
        System.out.println("StringBuilder aaa >>> "+sb.toString()); // aaabbb
    }

String 不可變的技術實現

打開JDK的源碼:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
    
    //other codes
}
  • String 類由關鍵字 final 修飾,說明該類不可繼承

  • char value[] 屬性也被 final 所修飾,說明 value 的引用在建立以後,就不能被改變

以上兩點並不能徹底實現 String 不可變 ,緣由在於:

final int[] value={1,2,3}
      int[] another={4,5,6};
value=another;    // 編譯器報錯,final不可變

value 被 final 修飾,只能保證引用不被改變,可是 value 所指向的堆中的數組,纔是真實的數據,只要可以操做堆中的數組,依舊能改變數據。【解釋:String其實是可變的】

final int[] value={1,2,3};
value[2]=100;  //這時候數組裏已是{1,2,100}
  • 全部的成員屬性均被 private 關鍵字所修飾

爲了實現 String 不可變,關鍵在於Java的開發者在設計和開發 String 的過程當中,沒有暴露任何的內部成員,與此同時 API 的設計是均沒有操做 value 的值 , 而是採用 new String() 的方式返回新的字符串,保證了 String 的不可變。

JDK String API 源碼:

public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);  //採用 new String() 的方式返回新的字符串
    }
    
    

    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);  //採用 new String() 的方式返回新的字符串
    }

整個String設成final禁止繼承,避免被其餘人繼承後破壞。因此String是不可變的關鍵都在底層的實現,而不是一個final。考驗的是工程師構造數據類型,封裝數據的功力。

String s = "abcd";
s = "abcdel";

String 不可變性的圖示:

clipboard.png

相關文章
相關標籤/搜索