1. 程式人生 > 其它 >String類為什麼被設計成不可變類

String類為什麼被設計成不可變類

1.享元模式:

1.共享元素模式,也就是說:一個系統中如果有多處用到了相同的一個元素,那麼我們應該只儲存一份此元素,而讓所有地方都引用這一個元素。

2.Java中String就是根據享元模式設計的,而那個儲存元素的地方就叫做 "字串常量池——String Pool"。

public class Apple {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a 
== b); // true System.out.println(a.equals(b)); // true System.out.println(a == c); // false System.out.println(a.equals(c)); // true } } // 因為String太過常用,JAVA類庫的設計者在實現時做了個小小的變化,即採用了享元模式。 // 每當生成一個新內容的字串時,他們都被新增到一個共享池中,當第二次再次生成同樣內容的字串例項時, // 就共享此物件,而不是建立一個新物件,但是這樣的做法僅僅適合於通過=符號進行的初始化。  
// 需要說明一點的是,在object中,equals()是用來比較記憶體地址的, // 但是String重寫了equals()方法,用來比較內容的,即使是不同地址,只要內容一致,也會返回true, // 這也就是為什麼a.equals(c)返回true的原因了。

2.享元模式分析:

int x = 10;

String y = "hello";

1). 首先,10 和 "hello"會在經過javac(或者其他編譯器)編譯過後變為Class檔案中constant_pool table的內容。

2). 當我們的程式執行時,也就是說JVM執行時,每個Class constant_pool table中的內容會被載入到JVM記憶體中的方法區中各自Class的Runtime Constant Pool。

3). 一個沒有被String Pool包含的Runtime Constant Pool中的字串(這裡是"hello")會被加入到String Pool中(HosSpot使用hashtable引用方式),步驟如下: 

  一是:在Java Heap中根據"hello"字面量create一個字串物件

  二是:將字面量"hello"與字串物件的引用在hashtable中關聯起來,鍵 - 值 形式是:"hello" = 物件的引用地址。

4). 當一個新的字串出現在Runtime Constant Pool 中時怎麼判斷需不需要在Java Heap中建立新物件呢?

  策略是這樣:

  會先去根據equals來比較Runtime Constant Pool 中的這個字串是否和String Pool中某一個是相等的(也就是找是否已經存在),

    如果有那麼就不建立,直接使用其引用;

    如此,就實現了享元模式,提高的記憶體利用效率。

3.設計成不可變類的好處:

優點:
  1.效率:例如字串常量池,字串常量池可以將一些字元常量放在常量池中重複使用,避免每次都重新建立相同的物件、節省儲存空間。

     但如果字串是可變的,此時相同內容的String還指向常量池的同一個記憶體空間,當某個變數改變了該記憶體的值時,其他遍歷的值也會發生改變。

  2.安全性:不可變物件天生是執行緒安全的,在不同執行緒共享物件,不需要同步機制,因為物件的值是固定的。

缺點:

  資源開銷,物件需要頻繁的修改屬性,則每一次修改都會新建立一個物件,產生大量的資源開銷。

 常見的不可變類:String Integer Long等型別。

4. 如何設計一個不可變類:

1. 類使用final修飾符修飾,保證類不能被繼承。

     如果類可以被繼承會破壞類的不可變性機制,只要繼承類覆蓋父類的方法並且繼承類可以改變成員變數值,那麼一旦子類以父類的形式出現時,不能保證當前類是否可變。

2.類的成員變數都應該是private final的,保證成員變數不可改變。

3.任何獲取/修改屬性的方法都不應作用於屬性本身。

    不提供修改成員變數的方法,例如setter方法。

  getter方法不能返回物件本身,要返回物件的拷貝,防止物件外洩。

  修改物件的屬性時要返回新物件

4.對成員變數的初始化通過構造器進行,並進行深拷貝。

    如果使用傳入的引數直接賦值,則傳遞的只是引用,仍然可以通過外部變數改變它的值。

5.Demo:

public static void main(String[] args) {
    String s1 = "ab";
    String s2 = "abc";
    String s3 = s1 + "c";
    System.out.println(s3 == s2); // false
    System.out.println(s3.equals(s2)); // true
}
// 1.Java 語言提供對字串串聯符號(”+”)和其他物件到字串的轉換的特殊支援。
// 2.字串串聯是通過 StringBuilder(或 StringBuffer)類及其 append 方法實現的,字串轉換是通過 toString 方法實現的。
// 3.在本題中,先在常量池中建立”ab“,地址指向s1,再建立”abc”,指向s2。
// 對於s3,先建立StringBuilder(或 StringBuffer)物件,通過append連線得到“abc”,再呼叫toString()轉換得到的地址指向s3,故(s3==s2)為false。

6. 使用String s = new String("hello");會建立幾個物件

首先,出現了字面量"hello",那麼去String Pool中查詢是否有相同字串存在,

因為程式就這一行程式碼所以肯定沒有,那麼就在Java Heap中用字面量"hello"首先建立1個String物件。

接著,new String("hello"),關鍵字new又在Java Heap中建立了1個物件,

然後呼叫接收String引數的構造器進行了初始化。

最終s的引用是這個String物件。