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物件。