effective java 3th item1:考慮靜態工廠方法代替構造器

傳統的方式獲取一個類的實例,是經過提供一個 public 構造器。這裏有技巧,每個程序員應該記住。一個類能夠對外提供一個 public靜態工廠方法 ,該方法只是一個樸素的靜態方法,不須要有太多複雜的邏輯,只須要返回該類的實例。java


這裏經過 Boolean (是原始類型 boolean 的包裝類)舉一個簡單的例子:程序員

public static Boolean valueOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE;
    }

這個方法,將一個 boolean 原始類型的值轉換爲 Boolean 對象引用。編程


值得注意的是,本條目中說的一個 靜態工廠方法 不一樣於 設計模式 的工廠模式1,一樣的,本條目中描述的靜態工廠方法在設計模式找不到對應的模式。設計模式


一個類能夠對外提供靜態工廠方法,來取代 public 的構造器,或者與 public 構造器並存,對外提供兩種方式獲取實例。用靜態工廠方法取代 public 構造器,既有優點也有缺點。api

優點體如今下面幾點:緩存

  1. 靜態工廠方法與構造器比起來,它能夠隨意命名,而非固定的與類的名字保持一致。框架

    若是一個構造器的參數自己,不能對將要返回的對象具備準確的描述。此時使用一個具備準確描述名字的靜態工廠方法是一個不錯的選擇。它能夠經過名字對將要返回的對象,進行準確的描述。使得使用的人能夠見名知意。dom

    舉個例子,BigInteger(int, int, Random) 構造器,返回的值多是素數,所以,這裏其實能夠有更好的表達,經過使用一個靜態工廠方法 BigInteger.probablePrime(int, int, Random) 該方法於 1.4 被加入。工具

    一個類只能有一個指定方法簽名2的構造器。一般咱們都知道如何繞過這限制,經過交換參數列表的順序,獲得不一樣的方法簽名。可是這是一個很糟糕的主意。這給使用 api 的開發人員形成負擔,他們將很難記住哪個方法簽名對應哪個對象的返回,最後每每都是錯誤的調用。閱讀代碼的人一樣也蒙圈,若是沒有相應的文檔告訴他們,不一樣的方法簽名對應的構造器返回的對象是什麼。性能

    靜態工廠方法不受上述限制,不須要去經過交換參數順序來彼此區分,由於它們能夠擁有本身的名字。所以,當一個類的多個構造器,方法簽名差很少,僅僅參數順序不同的時候,考慮使用靜態工廠方法,仔細的爲靜態工廠方法取名字,以區分它們之間的不一樣。

  2. 靜態工廠方法與構造器比起來,沒必要每次調用都建立新的對象

    這容許不可變的類使用預建立的實例3,或者在建立實例的時候,將實例緩存起來4,重複的使用該實例,避免建立不重要的重複對象。Boolean.valueOf(boolean) 方法使用該技巧,它永遠都不會建立對象,返回的都是預建立好的對象。這個技巧有點相似於設計模式中的享元模式5 。它能大幅度的提升性能,特別是在特定場景下:一些對象建立的時候,須要花費很大性能,而且這些對象常常被使用。

    該特性容許類在任什麼時候候,對其產生多少實例具備精確的控制。用這種技巧的類,被稱爲實例受控的類。這裏有幾個使用實例受控類的理由。實例受控容許一個類保證它是一個單例或者不可實例化的類。一樣的,實例受控,也能夠保證不可變類不會存在兩個相等的實例。

  3. 靜態工廠方法與構造器比起來,能夠返回該類的任意子類型的對象

    具備足夠的靈活性,在獲取對象的時候。能夠返回協變類型,在方法中使用該類的子類構造器建立對象,而後返回,同時對外不須要暴露這些子類對象,適合於面向接口編程。在 1.8 以前,接口中不能有靜態方法,針對狀況的慣例作法是,針對名爲 Type 類型的接口,它的靜態方法被放在一個不可實例化的類 Types6,典型的例子是 java.util.Collections 類,它經過靜態工廠方法,能夠構建返回各式各樣的集合:同步集合、不可修改的集合等等,可是返回的時候都是返回接口類型,具體實現類型不對外公開。

    // Collections 中非公開類,同步map
       private static class SynchronizedMap<K,V>
            implements Map<K,V>, Serializable {
            private static final long serialVersionUID = 1978198479659022715L;
    
            private final Map<K,V> m;     // Backing Map
            final Object      mutex;        // Object on which to synchronize
    
            SynchronizedMap(Map<K,V> m) {
                this.m = Objects.requireNonNull(m);
                mutex = this;
            }
    
            SynchronizedMap(Map<K,V> m, Object mutex) {
                this.m = m;
                this.mutex = mutex;
            }
    
        // Collections 的靜態工廠方法,返回接口接口map,可是內部是返回同步Map類型。
       public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
            return new SynchronizedMap<>(m);
        }

    起到簡化 api 的做用,這種簡化不只僅是 api 體積上的減小,減小對外暴露的類的數量,也給程序員直觀上的簡潔,他們只須要記住接口類型便可,無需記住具體的實現類型。這樣也督促程序員面向接口編程,這是一種好的習慣。

    1.8 之後,接口能夠寫靜態方法。所以,再也不須要按照之前的習慣,爲接口,寫一個對應的類,直接在接口中寫靜態工廠方法。可是關於返回的實現類型,依然應該繼續隱藏,使用非公開類實現。

  4. 靜態工廠方法與構造器比起來,能夠隨着傳入參數的不一樣,返回不一樣的對象。

    能夠返回任何子類型,和第三條同樣,可是能夠繼續添加控制,根據傳入參數的不一樣,返回不一樣的對象。

    一個例子,EnumSet ,是一個不可實例的類,只有一個靜態工廠方法,沒有構造器。可是在 openJDK 的實現中,具體的返回類型,是根據實際枚舉的個數決定的,若是小於等於 64,則返回 RegularEnumSet 類型,不然返回 JumboEnumSet 類型。這兩個類型對於使用者來講,都是不可見的。若是 RegularEnumSet 類型,在未來再也不爲小的枚舉類型提供優點,即使在將來的發行版刪除 RegularEnumSet 也不會有什麼影響。一樣的,將來也能夠繼續添加第三個、第四個版本,對使用者也是無感的。使用者不須要關心具體返回的是什麼對象,他們只知道,返回的對象都是 EnumSet 類型。

    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");
    
            // 若是小於等於 64 ,則返回 RegularEnumSet
            if (universe.length <= 64)
                return new RegularEnumSet<>(elementType, universe);
            else
                return new JumboEnumSet<>(elementType, universe);
        }
    
        // RegularEnumSet 類是 EnumSet的子類
        class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
    
            ...
        }
  5. 靜態工廠方法與構造器比起來,在編寫靜態工廠方法的時候,具體的類型能夠不存在。

    這句話仍是面向接口的優點,意思就是,咱們在編寫方法的時候,能夠沒有任何實現類,在使用的使用,先註冊實現類,而後再返回實現類,這使得擴展變得很容易。咱們只是在維護一個框架,一個接口,具體的實現,咱們不給出,誰均可以實現,而後註冊使用。這也是 服務者框架 的含義。JDBC 就是這麼一個思想的服務者框架。關於服務者框架看這裏

缺點:

  1. 靜態工廠方法與構造器比起來,沒有 public 或者 protected 修飾的構造器,沒法實現繼承

    例如,上面提到的 Collections ,就沒法被繼承,咱們就不能繼承其中的任何一個便利的實現。這或許,也是一種對使用組合而非繼承的鼓勵。

  2. 靜態工廠方法與構造器比起來,它們不容易被程序員所知曉

    在文檔中,它們不像構造器那麼顯眼,在上面單獨的列出來,基於這個緣由,要想知道如何實例化一個類,使用靜態工廠方法比使用構造器相比,前者是比較困難的,由於文檔中,靜態工廠方法和其餘靜態方法沒啥區別,沒有作特殊處理。java 文檔工具或許在將來會注意到這個問題,對靜態工廠方法多給予一些關注。

    同時,咱們能夠在文檔中對靜態工廠方法的名字作一些特殊處理,遵照常見的命名規範,來減小這個問題,好比像下面提到的幾個規範:

    1. from類型轉換方法,根據一個單一傳入的參數,返回一個對應的類型,好比:Date d = Date.from(instant);
    2. of聚合方法,接受多個參數,返回一個合併它們的實例。好比:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
    3. valueOf比 from 和 of 更加詳細 。好比:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
    4. instance or getInstance獲取實例方法,根據傳入的參數,返回實例,可是不保證返回的實例徹底同樣,根據傳入的參數不一樣而不一樣。好比:StackWalker luke = StackWalker.getInstance(options);
    5. create or newInstance獲取新的實例,每次都返回新建立的實例。好比:Object newArray = Array.newInstance(classObject, arrayLen);
    6. getType ,和 getInstance 相似,用於返回的類型,不是靜態工廠方法所在的類,而是其餘類型。好比:FileStore fs = Files.getFileStore(path);
    7. newTypenewInstance 類型,一樣用於返回的類型,不是靜態工廠方法所在的類,而是其餘類型。好比:BufferedReader br = Files.newBufferedReader(path);
    8. typegetType and newType 的簡化版。好比:List<Complaint> litany = Collections.list(legacyLitany);

總結下,靜態工廠方法和 public 構造器都有本身的優勢,瞭解它們各自的優勢是有幫助的。大部分狀況下,靜態工廠方法更佔優點,因此,咱們應該避免第一反應就使用構造器,而是先考慮下靜態工廠方法。



  1. 這裏的靜態工廠方法,和設計模式中的靜態工廠模式,很類似,可是設計模式中的靜態工廠模式,它是對外隱藏對象的實現細節,經過一個工廠,根據不一樣的輸入,產生不一樣的輸出。本條目中的工廠,只會產生特定的輸出,即本身的實例。兩者仍是不一樣的。

  2. 方法簽名,指的是方法名字,以及方法參數列表,包括方法參數的順序。

  3. 相似於單例模式的餓漢式

  4. 相似於單例模式的懶漢式

  5. 享元模式,23種設計模式中的一種, 它針對每一種內部狀態僅提供一個對象,設置不一樣的外部狀態,產生多種不一樣的形態,可是其內部狀態對象只有一個,達到對象複用的目的。

  6. 這裏的 Tyle 類型,不是泛型的 Type ,只是一種代指,跟 xxx 一個意思,表示 1.7 之前,對於面向接口編程的時候,想要返回協變類型,常規的作法,是寫一個不可實現類 ,類的名字,就是接口的名字,多加一個 s

相關文章
相關標籤/搜索