關於不可變類,你真的瞭解了嗎

提到不可變類,大家的第一反應一定就是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類是不可變類

自定義不可變類

如果需要創建自定義的不可變類,可遵守如下規則。

  1. 使用private和final修飾符來修飾該類的Field。
  2. 提供帶參數構造器,用於根據傳入參數來初始化類裏的Field。
  3. 僅爲該類的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對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章