1. 程式人生 > >Java-快取不可變類

Java-快取不可變類

不可變類

  • 不可變類的意思是建立該類例項後,該例項的例項變數是不可改變的

    • Java中的8個包裝類java.lang.String類都是不可變類,當建立它們的例項後,其例項變數不可改變。
  • 如果需要建立自定義的不可變類,遵循以下規則:

    • 使用privatefinal修飾符來修飾該類的成員變數

    • 提供帶引數構造器,用於根據傳入引數來初始化類中的成員變數

    • 僅為該類的成員變數提供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
    n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return
    true; } } return false; }

    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物件不是同一個。