提到不可變類,大家的第一反應一定就是String類了,沒錯,String類就是不可變類,可大家真的理解了不可變類的意義了嗎,還是說final class String 就代表了不可變類了?非也,今天我們就一起來看看何爲不可變類
概念
不可變類的意思是創建該類的實例後,該實例的Field是不可改變的。
也就是說是該類的實例Field不可變才說明該類是不可變類,並不是說final修飾的類就是不可變類,final修飾的類只保證了該類不可被繼承而已
java提供的8個包裝類和java.lang.String類都是不可變類,當創建它們的實例後,其實例的Field不可改變
拿Double類來說
Double d = new Double(6.5);
查看Double類源碼
private final double value;
/**
* Constructs a newly allocated {@code Double} object that
* represents the primitive {@code double} argument.
*
* @param value the value to be represented by the {@code Double}.
*/
public Double(double value) {
this.value = value;
}
如上圖Double源碼,當創建Double實例的時候實際上是在Double構造器中初始化了其成員變量final double value
,該value成員變量被final修飾(final修飾的變量一旦被初始化了則不能再被賦值
),所以該Double實例的值是不會被改變的
由此驗證Double類是不可變類
自定義不可變類
如果需要創建自定義的不可變類,可遵守如下規則。
- 使用private和final修飾符來修飾該類的Field。
- 提供帶參數構造器,用於根據傳入參數來初始化類裏的Field。
- 僅爲該類的Field提供getter方法,不要爲該類的Field提供setter方法,提供了也沒法修改
(反射情況除外)
。
下面例子驗證了反射可以修改final修飾的成員變量
System.out.println("修改前:" + d);
Class c = Double.class;
Field f = c.getDeclaredField("value");
f.setAccessible(true);
f.set(d, 6.6);
System.out.println("修改後:" + d);
修改前:6.5
修改後:6.6
與不可變類對應的是可變類,可變類的含義是該類的實例Field是可變的。大部分時候所創建的類都是可變類,特別是JavaBean,因爲總是爲其Field提供了setter和getter方法。
與可變類想比,不可變類的實例在整個生命週期中永遠處於初始化狀態,它的Field不可改變。因此對不可變類的實例的控制將更加簡單。
緩存實例的不可變類
不可變類的實例狀態不可改變(通俗點講就是成員變量不可改變
),可以很方便地被多個對象所共享。如果程序經常需要使用相同的不可變類實例,則應該考慮緩存這種不可變類的實例。畢竟重複創建相同的對象沒有太大的意義,而且加大系統開銷。如果可能,應該將已經創建的不可變類的實例進行緩存。
如大家所知的字符串常量池機制就是一種緩存機制,String類是不可變類,所以字符串不可修改,那麼就沒必要重複創建同一個字符串,我不能修改這個對象幹嘛要創建多個相同的字符串呢?這樣就達到了節約內存資源的目的,也可以提高效率
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];
}
@Override
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;
}
@Override
public int hashCode(){
return name.hashCode();
}
}
上述代碼輸出結果爲true
上面CacheImmutale類使用一個數組來緩存該類的對象,這個數組長度爲MAX_SIZE,即該類共可以緩存MAX_SIZE個CacheImmutale對象。當緩存池已滿時,緩存池採用"先進先出"規則來決定哪個對象將被移出緩存池.
CacheImmutale類能控制系統生成CacheImmutale對象的個數,需要程序使用該類的valueOf方法來得到其對象,而且程序使用private修飾符隱藏該類的構造器,因此程序只能通過該類提供的valueOf方法來獲取實例.
是否需要隱藏CacheImmutale類的構造器完全取決於系統需求。盲目亂用緩存也可能導致系統性能下降,緩存的對象會佔用系統內存,如果某個對象只使用一次,重複使用的概率不大,緩存該實例就弊大於利;反之,如果某個對象需要頻繁地重複使用,緩存該實例就利大於弊。
如java提供的java.lang.Integer類,它就採用了與CacheImmutale類相同的處理策略,如果採用new構造器來創建Integer對象,則每次返回全新的Integer對象;如果採用valueOf方法來創建Integer對象,則會緩存該方法創建的對象。
Integer in1 = Integer.valueOf(6);
Integer in2 = Integer.valueOf(6);
System.out.println(in1 == in2);
以上代碼輸出結果爲true
注意:
Integer只緩存了-128~127之間的Integer對象