天天看點

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中會把構造方法單獨拿出來,你一下就知道是可以建立對象的,但是靜态工廠方法就不太容易找喽。

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