1. 程式人生 > >1. 【建立與銷燬物件】考慮用靜態工廠方法代替構造器

1. 【建立與銷燬物件】考慮用靜態工廠方法代替構造器

本文內容為《Effective Java》的讀書筆記。

對於某一個類,要獲取其物件,通常是使用共有的構造方法new一個。其實還有另一種方法也是經常用到的,那就是靜態工廠方法

(注:此處的靜態工廠方法不同於工廠模式)

舉個例子:

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

這是java.lang.Integer的一個方法,通過給定一個int型的值得到相應的Integer物件。

相對於建構函式來說,靜態工廠方法有什麼好處呢?

1. 有明確的方法名。

直接上例子,例如構造器BigInteger(int, int, Random)返回的BigInteger可能為素數,如果用BigInteger.probablePrime的靜態工廠方法表示則更清楚。

有的構造器有多個引數,如果單純通過引數型別和順序建立物件,經常記不住改用哪個構造器,這時候靜態工廠方法便可發揮作用了。

2. 當不必每次都建立新的物件的時候。

您肯定首先就能想到單例模式,沒錯,通過靜態工廠方法每次都獲取特定的物件例項。

還有別的情況,比如剛才的valueOf方法,仔細看程式碼發現實際上返回的物件是做了快取的,也就是從IntegerCache.low到IntegerCache.high之間的數字是快取在IntegerCache中的,而IntegerCache中維護了一個靜態的Integer陣列cache[],比如當呼叫方法valueOf(123)的時候,會檢視cache[]中是否有值為123的Integer物件,如果沒有,那麼建立一個Integer(123)返回,並儲存到cache[]中;如果有,那麼直接取出該物件返回。因此,兩次valueOf(123)返回的物件是同一個,這樣能有效減少物件個數。預設狀態下,Integer是對-128到127的數字進行快取的。

聰明的你可能接著就想到了Boolean.valueOf(boolean),沒錯,返回的物件始終只有那兩個。

3. 可以返回原返回型別的子型別的物件。

這種靈活性的應用是,API可以返回物件,同時又不會使物件的類變成共有的。以這種方式隱藏實現類會是API變得非常簡潔。

似懂非懂?那就直接看看java.util.Collections的原始碼吧,這個類是不能被例項化的,其內定義了數十個不同型別的集合(不可修改集合、空集合、同步集合等),同時有響應的靜態工廠方法來獲取這些集合。隨便點開一個方法:

    public static <T> Set<T> unmodifiableSet(Set<? extends T> s) {
        return new UnmodifiableSet<>(s);
    }
返回的物件所屬的類UnmodifiableSet就是在Collections類中定義的,但方法返回值是Set型別,也就是UnmodifiableSet的介面型別。可見,這種方式適用於基於介面的框架。

通過這種方式,這數十個不同型別的集合無需再定義獨立的共有類了,這不僅僅是API數量的減少,也是概念意義上的減少。這時候客戶端被要求通過介面來引用建立的物件,而不是具體的實現類,這是一種良好的習慣。

再舉一個例子:

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
        Enum<?>[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + " not an enum");

        if (universe.length <= 64)
            return new RegularEnumSet<>(elementType, universe);
        else
            return new JumboEnumSet<>(elementType, universe);
    }
這是EnumSet類中的一個靜態工廠方法,用於根據元素型別產生一個空EnumSet,並且根據不同的元素個數,建立不同的具體實體類物件(當元素個數少於64個的時候返回RegularEnumSet物件,否則返回JumboEnumSet),而使用者完全不用關心物件是什麼具體型別的,只要能滿足EnumSet的功能就OK。

更有些時候,在編寫靜態工廠方法所在的類的時候,具體的實現類還不存在呢。比如JDBC的API,不同的driver對應不同的資料庫操作方式,但是我們不用關心,只知道增刪改查即可,具體MySQL還是Oracle還是又新出的資料庫產品它的增刪改查操作的實現細節,那是資料庫產品廠商提供的。這就是服務提供者框架的理念。

4. 在建立引數化例項的時候,它們使程式碼變得更加簡潔。
即使是Java初學者,應該也寫過類似下邊的程式碼:

    Map<String, List<String>> map = new HashMap<String, List<String>>();
這還算好的,如果型別更加複雜,那麼這句話會更長,怪不得總是被吐槽Java語言冗長。

假設HashMap提供了這樣的方法:

    public static <K, V> HashMap<K, V> newInstance() {
        return new HashMap<K, V>();
    }
那麼就可以用如下程式碼來建立物件了:
    Map<String, List<String>> map = HashMap.newInstance();

不過現在的Java版本已經具有型別推斷的能力了,後邊的<>中可以空著。

當然,靜態工廠方法也存在一些不足:

1. 有些含有靜態工廠方法的類,通常不再提供共有的或受保護的構造方法,因此也就不能被子類化了。

比如剛才提到的Collections中的實體類,他們就不能被子類化了。不過這樣也是因禍得福,鼓勵開發人員更多使用組合而不是繼承。

2. 它們與其他的靜態方法實際上並沒有任何區別。

比如Java doc中會把構造方法單獨拿出來,你一下就知道是可以建立物件的,但是靜態工廠方法就不太容易找嘍。

好吧,感覺這兩點不足相對於它的優勢來說,實在不足掛齒,在我們的開發過程中,如果能更加自如的應用靜態工廠方法,會讓程式碼的結構更佳、逼格更高喲。