傳統的方式獲取一個類的實例,是經過提供一個 public
構造器。這裏有技巧,每個程序員應該記住。一個類能夠對外提供一個 public
的 靜態工廠方法 ,該方法只是一個樸素的靜態方法,不須要有太多複雜的邏輯,只須要返回該類的實例。java
這裏經過 Boolean
(是原始類型 boolean
的包裝類)舉一個簡單的例子:程序員
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
這個方法,將一個 boolean
原始類型的值轉換爲 Boolean
對象引用。編程
值得注意的是,本條目中說的一個 靜態工廠方法 不一樣於 設計模式 的工廠模式1,一樣的,本條目中描述的靜態工廠方法在設計模式找不到對應的模式。設計模式
一個類能夠對外提供靜態工廠方法,來取代 public
的構造器,或者與 public
構造器並存,對外提供兩種方式獲取實例。用靜態工廠方法取代 public
構造器,既有優點也有缺點。api
優點體如今下面幾點:緩存
靜態工廠方法與構造器比起來,它能夠隨意命名,而非固定的與類的名字保持一致。框架
若是一個構造器的參數自己,不能對將要返回的對象具備準確的描述。此時使用一個具備準確描述名字的靜態工廠方法是一個不錯的選擇。它能夠經過名字對將要返回的對象,進行準確的描述。使得使用的人能夠見名知意。dom
舉個例子,BigInteger(int, int, Random)
構造器,返回的值多是素數,所以,這裏其實能夠有更好的表達,經過使用一個靜態工廠方法 BigInteger.probablePrime(int, int, Random)
該方法於 1.4
被加入。工具
一個類只能有一個指定方法簽名2的構造器。一般咱們都知道如何繞過這限制,經過交換參數列表的順序,獲得不一樣的方法簽名。可是這是一個很糟糕的主意。這給使用 api
的開發人員形成負擔,他們將很難記住哪個方法簽名對應哪個對象的返回,最後每每都是錯誤的調用。閱讀代碼的人一樣也蒙圈,若是沒有相應的文檔告訴他們,不一樣的方法簽名對應的構造器返回的對象是什麼。性能
靜態工廠方法不受上述限制,不須要去經過交換參數順序來彼此區分,由於它們能夠擁有本身的名字。所以,當一個類的多個構造器,方法簽名差很少,僅僅參數順序不同的時候,考慮使用靜態工廠方法,仔細的爲靜態工廠方法取名字,以區分它們之間的不一樣。
靜態工廠方法與構造器比起來,沒必要每次調用都建立新的對象
這容許不可變的類使用預建立的實例3,或者在建立實例的時候,將實例緩存起來4,重複的使用該實例,避免建立不重要的重複對象。Boolean.valueOf(boolean)
方法使用該技巧,它永遠都不會建立對象,返回的都是預建立好的對象。這個技巧有點相似於設計模式中的享元模式5 。它能大幅度的提升性能,特別是在特定場景下:一些對象建立的時候,須要花費很大性能,而且這些對象常常被使用。
該特性容許類在任什麼時候候,對其產生多少實例具備精確的控制。用這種技巧的類,被稱爲實例受控的類。這裏有幾個使用實例受控類的理由。實例受控容許一個類保證它是一個單例或者不可實例化的類。一樣的,實例受控,也能夠保證不可變類不會存在兩個相等的實例。
靜態工廠方法與構造器比起來,能夠返回該類的任意子類型的對象
具備足夠的靈活性,在獲取對象的時候。能夠返回協變類型,在方法中使用該類的子類構造器建立對象,而後返回,同時對外不須要暴露這些子類對象,適合於面向接口編程。在 1.8
以前,接口中不能有靜態方法,針對狀況的慣例作法是,針對名爲 Type
類型的接口,它的靜態方法被放在一個不可實例化的類 Types
中6,典型的例子是 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
之後,接口能夠寫靜態方法。所以,再也不須要按照之前的習慣,爲接口,寫一個對應的類,直接在接口中寫靜態工廠方法。可是關於返回的實現類型,依然應該繼續隱藏,使用非公開類實現。
靜態工廠方法與構造器比起來,能夠隨着傳入參數的不一樣,返回不一樣的對象。
能夠返回任何子類型,和第三條同樣,可是能夠繼續添加控制,根據傳入參數的不一樣,返回不一樣的對象。
一個例子,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> { ... }
靜態工廠方法與構造器比起來,在編寫靜態工廠方法的時候,具體的類型能夠不存在。
這句話仍是面向接口的優點,意思就是,咱們在編寫方法的時候,能夠沒有任何實現類,在使用的使用,先註冊實現類,而後再返回實現類,這使得擴展變得很容易。咱們只是在維護一個框架,一個接口,具體的實現,咱們不給出,誰均可以實現,而後註冊使用。這也是 服務者框架 的含義。JDBC
就是這麼一個思想的服務者框架。關於服務者框架看這裏
缺點:
靜態工廠方法與構造器比起來,沒有 public
或者 protected
修飾的構造器,沒法實現繼承
例如,上面提到的 Collections
,就沒法被繼承,咱們就不能繼承其中的任何一個便利的實現。這或許,也是一種對使用組合而非繼承的鼓勵。
靜態工廠方法與構造器比起來,它們不容易被程序員所知曉
在文檔中,它們不像構造器那麼顯眼,在上面單獨的列出來,基於這個緣由,要想知道如何實例化一個類,使用靜態工廠方法比使用構造器相比,前者是比較困難的,由於文檔中,靜態工廠方法和其餘靜態方法沒啥區別,沒有作特殊處理。java
文檔工具或許在將來會注意到這個問題,對靜態工廠方法多給予一些關注。
同時,咱們能夠在文檔中對靜態工廠方法的名字作一些特殊處理,遵照常見的命名規範,來減小這個問題,好比像下面提到的幾個規範:
from
,類型轉換方法,根據一個單一傳入的參數,返回一個對應的類型,好比:Date d = Date.from(instant);
。of
, 聚合方法,接受多個參數,返回一個合併它們的實例。好比:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf
,比 from 和 of 更加詳細 。好比:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance
,獲取實例方法,根據傳入的參數,返回實例,可是不保證返回的實例徹底同樣,根據傳入的參數不一樣而不一樣。好比:StackWalker luke = StackWalker.getInstance(options);
。create or newInstance
,獲取新的實例,每次都返回新建立的實例。好比:Object newArray = Array.newInstance(classObject, arrayLen);
。getType
,和 getInstance
相似,用於返回的類型,不是靜態工廠方法所在的類,而是其餘類型。好比:FileStore fs = Files.getFileStore(path);
。newType
和 newInstance
類型,一樣用於返回的類型,不是靜態工廠方法所在的類,而是其餘類型。好比:BufferedReader br = Files.newBufferedReader(path);
。type
,getType and newType
的簡化版。好比:List<Complaint> litany = Collections.list(legacyLitany);
。總結下,靜態工廠方法和 public
構造器都有本身的優勢,瞭解它們各自的優勢是有幫助的。大部分狀況下,靜態工廠方法更佔優點,因此,咱們應該避免第一反應就使用構造器,而是先考慮下靜態工廠方法。
這裏的靜態工廠方法,和設計模式中的靜態工廠模式,很類似,可是設計模式中的靜態工廠模式,它是對外隱藏對象的實現細節,經過一個工廠,根據不一樣的輸入,產生不一樣的輸出。本條目中的工廠,只會產生特定的輸出,即本身的實例。兩者仍是不一樣的。↩
方法簽名,指的是方法名字,以及方法參數列表,包括方法參數的順序。↩
相似於單例模式的餓漢式↩
相似於單例模式的懶漢式↩
享元模式,23種設計模式中的一種, 它針對每一種內部狀態僅提供一個對象,設置不一樣的外部狀態,產生多種不一樣的形態,可是其內部狀態對象只有一個,達到對象複用的目的。↩
這裏的 Tyle
類型,不是泛型的 Type
,只是一種代指,跟 xxx
一個意思,表示 1.7
之前,對於面向接口編程的時候,想要返回協變類型,常規的作法,是寫一個不可實現類 ,類的名字,就是接口的名字,多加一個 s
。↩