Java-快取不可變類
不可變類
不可變類的意思是建立該類例項後,該例項的
例項變數是不可改變的
。- Java中的
8個包裝類
和java.lang.String類
都是不可變類,當建立它們的例項後,其例項變數
不可改變。
- Java中的
如果需要建立自定義的不可變類,遵循以下規則:
使用
private
和final
修飾符來修飾該類的成員變數
提供
帶引數構造器
,用於根據傳入引數來初始化類中的成員變數
僅為該類的成員變數
提供getter方法
,不要
為該類的成員變數提供setter方法,因為普通方法無法修改final修飾的成員變數如果有必要,重寫hashCode()和equals()。
equals()方法根據關鍵成員變數來作為兩個物件是否相等的標準
- 應保證
兩個用equals()方法判斷為相等的物件的hashCode()也相等
例如String類物件裡的
字元序列作為相等的標準
,其hashCode()
方法也是根據字元序列
計算得到的。String類中的
equals()
方法原始碼public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int
String類中的hashCode()方法的原始碼:
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
可變類
的含義是該類的例項變數
是可變的
。大部分建立的類都是可變類,特別是JavaBean,因為總是為其例項變數提供了setter和getter方法。與可變類相比,
不可變類的例項在整個生命週期中永遠處於初始化狀態
,它的例項變數不可改變。因此對不可變類的例項的控制將更加簡單。如果需要設計一個不可變類,尤其要注意其
引用型別的成員變數
,如果引用型別的成員變數的類是可變的
,就必須採取必要的措施來保護該成員變數所引用的物件不會被修改
,這樣才能建立真正的不可變類。
快取不可變類
不可變類的例項狀態不可改變,可以很
方便的被多個物件共享
。如果程式經常使用相同的不可變例項,就應該考慮快取
這種不可變類的例項
。畢竟重複建立相同的物件沒有意義,而且會加大系統開銷
。用
陣列建立快取池
,用於快取例項:class CacheImmutale { private static int MAX_SIZE = 10; // 使用陣列來快取已有的例項 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; // 記錄快取例項在快取中的位置,cache[pos-1]是最新快取的例項 private static int pos = 0; private final String name; private CacheImmutale(String name) { this.name = name; } public String getName() { return name; } public static CacheImmutale valueOf(String name) { // 遍歷已快取的物件, for (int i = 0 ; i < MAX_SIZE; i++) { // 如果已有相同例項,直接返回該快取的例項 if (cache[i] != null && cache[i].getName().equals(name)) { return cache[i]; } } // 如果快取池已滿 if (pos == MAX_SIZE) { //先進先出 // 把快取的第一個物件覆蓋,即把剛剛生成的物件放在快取池的最開始位置。 cache[0] = new CacheImmutale(name); // 把pos設為1 pos = 1; } else { // 把新建立的物件快取起來,pos加1 cache[pos++] = new CacheImmutale(name); } return cache[pos - 1]; } public boolean equals(Object obj) { if(this == obj) { return true; } if (obj != null && obj.getClass() == CacheImmutale.class) { CacheImmutale ci = (CacheImmutale)obj; return name.equals(ci.getName()); } return false; } public int hashCode() { return name.hashCode(); } } public class CacheImmutaleTest { public static void main(String[] args) { CacheImmutale c1 = CacheImmutale.valueOf("hello"); CacheImmutale c2 = CacheImmutale.valueOf("hello"); // 下面程式碼將輸出true System.out.println(c1 == c2); } }
是否需要隱藏快取池類的構造器完全取決於系統需求。盲目亂用快取也可能導致系統性能下降,快取的物件會佔用系統記憶體,如果某個物件只使用一次,重複使用的概率不大,快取該例項就弊大於利;反之,如果某個物件需要頻繁地重複使用,快取該例項就利大於弊。
Java中
Integer
類就採取了上述CacheImmutale類相同的處理策略,如果採用new構造器
來建立Integetr物件,則每次返回全新的Integer物件
;如果採用valueOf()方法來建立Integer物件
,則會快取該方法建立的物件
。由於new構造器方式建立Integer物件不會啟用快取,因此效能較差,所以
Java9
中已經將該構造器標記為過時,全面採用valueOf()方法建立。public class IntegerCacheTest { public static void main(String[] args) { // 生成新的Integer物件 Integer in1 = new Integer(6); // 生成新的Integer物件,並快取該物件 Integer in2 = Integer.valueOf(6); // 直接從快取中取出Ineger物件 Integer in3 = Integer.valueOf(6); System.out.println(in1 == in2); // 輸出false System.out.println(in2 == in3); // 輸出true // 由於Integer只快取-128~127之間的值, // 因此200對應的Integer物件沒有被快取。 Integer in4 = Integer.valueOf(200); Integer in5 = Integer.valueOf(200); System.out.println(in4 == in5); //輸出false } }
- 由於Integer只快取-128~127之間的Integer物件,因此兩次通過
Integer.valueOf(200)
方法生成的Integer物件不是同一個。
- 由於Integer只快取-128~127之間的Integer物件,因此兩次通過