類容許客戶端獲取實例的傳統方法是提供公共構造器。還有一種技術應該是每一個程序員的工具箱的一部分。一個類能夠提供一個公共靜態工廠方法,它僅僅是一個返回類實例的靜態方法。下面是布爾(布爾型的盒裝原語類)的一個簡單示例。這個方法將一個布爾原始值轉換成布爾對象引用:java
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
注意: 靜態工廠方法與設計模式 [Gamma95] 的工廠方法模式不一樣,在這個Item中描述的靜態工廠方法並不等同於設計模式中的工廠方法模式。程序員
一個類能夠爲其客戶提供靜態工廠方法,而不是公共構造函數。提供靜態工廠方法而不是公共構造函數既有優勢也有缺點。數據庫
靜態工廠方法的一個優勢是,它們是有名稱的,而構造函數的名稱都是同樣的。 若是構造函數的參數自己不能描述返回的對象,那麼使用一個精心命名的靜態工廠更容易使用,而且生成的客戶端代碼更容易閱讀。例如BigInteger
的一個構造函數:BigInteger(int, int, Random)
,這個構造函數返回一個BigInteger
有多是一個質數,使用一個精心命名的靜態工廠方法會更容易描述該方法返回的BigInteger類型,好比BigInteger.probablePrime
(這是在Java 4 中添加的)。編程
一個類只能有一個帶有給定簽名的構造函數。程序員能夠經過提供兩個構造函數來繞過這個限制,這些構造函數的參數列表只在參數類型的順序上有所不一樣。這是個至關壞的主意。這樣使用這個API的用戶永遠沒法記住應該使用哪一個構造函數,而且最終會調用錯誤的構造函數。使用這些構造函數的人在不閱讀的引用類文檔的狀況下是不知道代碼是幹什麼的。設計模式
由於構造函數有名稱,因此就不會有這個限制,在一個類須要使用多個簽名、多個構造函數的狀況下,用靜態工廠方法代替構造函數,並仔細選擇方法的名稱就能夠突出構造函數之間的差別了。數組
靜態工廠方法的第二個優勢是,與構造函數不一樣,它們不須要在每次被調用時建立一個新對象。 這容許不可變類(第17項)使用預先構造的實例,或者在構建時緩存實例,並重復分發它們,以免建立沒必要要的重複對象。Boolean.valueOf(boolean)
方法使用了這種方式:它從不建立對象。這種技術相似於享元模式[Gamma95]。若是常常請求等效對象,特別是當它們的建立成本很高時,它能夠極大地提升性能。緩存
靜態工廠方法從重複調用中返回相同的對象的能力容許類在任什麼時候候保持對實例的嚴格控制。這樣作的類被認爲是實例控制的。編寫實例控制類的緣由有幾個。實例控制容許一個類保證它是一個單例(第3項)或非實例化的(第4項),而且它容許一個不可值的類(第17項)來保證沒有兩個相等的實例存在:當且僅當a==b
成立時,a.equals(b)
返回true
,這是享元模式 [Gamma95] 的基礎,枚舉類型就提供了這種保證。框架
靜態工廠方法的第三個優勢是,與構造函數不一樣,它們能夠的對象能夠是返回類型的任何子類的實例對象。 這使在選擇返回的對象的類時具備很大的靈活性。dom
這種靈活性的一個應用是,API能夠返回對象,同時又不會使對象的類編程公有的,以這種方式隱藏實現類會使API變得很是簡潔。這種技術適用於基於接口的框架(interface-based frameworks,見第20項),由於在這種框架中,接口爲靜態工廠方法提供了天然返回類型。ide
在Java 8 以前,接口不能有靜態方法。按照慣例,名爲Type的接口的靜態工廠方法被放置在一個不可實例化的名爲Types的配套類(noninstantiable companion class)(第4項)中。例如,Java Collections Framework有45個便利實現,分別提供了不可修改的集合、同步集合等等。幾乎全部這些實現都經過靜態工廠方法在一個不可實例化的類(java.util.Collections)中導出。全部返回對象的類都是非公有的。
如今的Collections Framework API比導出的45個獨立的公有類的那種實現方式要小得多,每種便利的實現都對應一個類。這不只僅減小了API的數量,還包括概念上的權重:程序猿必須掌握的概念的數量和難度,以便使用API。程序猿知道返回的對象正好有其接口指定的API,所以不須要爲實現類去閱讀額外的類文檔。此外,這種工廠方法要求客戶端經過接口而不是實現類來引用返回的對象,這一般是很好的實踐方式(第64項)。
從Java 8開始,消除了接口不能包含靜態方法的限制,所以一般沒有理由爲接口提供不可實例化的伴隨類。許多公共靜態成員應該放在接口自己中。但請注意,可能仍有必要將大量實現代碼放在這些靜態方法後面的單獨的包私有類中。這是由於Java 8要求接口的全部靜態成員都是公共的。 Java 9容許私有靜態方法,但靜態字段和靜態成員類的屬性依然是要求是公共的。
靜態工廠方法的第四個優勢是,靜態工廠方法所返回的對象的類能夠隨着每次調用而變化,這取決於靜態工廠方法的參數值。 只要返回的類型是聲明的類的子類都是容許的。返回對象的類也可能隨着發行版本的不一樣而不一樣。
在EnumSet
類(第36項)中有非公有的構造方法,只有靜態工廠方法。在 OpenJDK 實現中的,它們返回兩個子類之一的實例,具體取決於基礎枚舉類型的大小: 若是它有64個或更少的元素,就像大多數枚舉類型所作的那樣,靜態工廠返回一個RegularEnumSet
實例, 它由單個long
的支持;若是枚舉類型有65個或更多元素,則工廠將返回一個由長數組支持的JumboEnumSet
實例。
這兩個實現類的存在對於客戶端是不可見的。 若是 RegularEnumSet 再也不爲小枚舉類型提供性能優點能夠從將來版本中刪除,沒有任何不良影響。 一樣,將來若是證實有利於性能,則能夠添加EnumSet
的第三或第四個實現。客戶即不知道也不關心他們從工廠中獲取的對象的類型,他們只關心它是EnumSet
的一些子類。
靜態工廠方法的第五個優勢是,返回的對象所屬的類,在編寫包含該靜態工廠方法的類時能夠沒必要存在。 這種靈活的靜態工廠方法構成了服務提供者框架(Service Provider Framework)的基礎,例如JDBC(Java數據庫鏈接,Java Database Connectivity)API。服務提供者框架是提供者實現服務的系統,系統使實現可用於客戶端,將客戶端與實現分離【微服務】。
服務提供者框架中有三個基本組件:服務接口【提供者】,表明一個實現;提供者註冊API【註冊中心】,提供者用於註冊實現; 以及服務訪問API【消費者】,客戶端使用它來獲取服務的實例。 服務訪問API能夠容許客戶端指定用於選擇實現的標準。 若是沒有這樣的標準,API將返回默認實現的實例,或容許客戶端循環遍歷全部可用的實現。 服務訪問API是靈活的靜態工廠,它構成了服務提供者框架的基礎。
服務提供者框架的可選第四個組件是服務提供者接口,它描述了生成服務接口實例的工廠對象。 在缺乏服務提供者接口的狀況下,必須反覆實例化實現(第65項)。 對於JDBC,Connection扮演服務接口的一部分,DriverManager.registerDriver是提供者註冊API,DriverManager.getConnection是服務訪問API,Driver是服務提供者接口。
服務提供者框架模式有許多變體。 例如,服務訪問API能夠向客戶端返回比提供者提供的服務接口更豐富的服務接口。 這是橋接模式 [Gamma95] 。 依賴注入框架(第5項)可視爲強大的服務提供者。 從Java 6開始,該平臺包含一個通用服務提供程序框架java.util.ServiceLoader,所以您不須要(一般不該該)本身編寫(第59項)。 JDBC不使用ServiceLoader,由於前者早於後者。
靜態工廠方法的主要限制在於,類若是不含公有的或者受保護的構造器,就不能被子類化。 例如:不可能將Collections Framework中的任何方便的實現類子類化。可是這也許會塞翁失馬,由於它鼓勵程序猿使用組合,而不是繼承(第18項),而且要求必須是不可變的(第17項)。
靜態工廠方法的第二個缺點是程序員很難找到它們。 它們不像構造函數那樣在API文檔中脫穎而出,所以很難弄清楚如何實例化提供靜態工廠方法而不是構造函數的類。 Javadoc工具備一天可能會引發對靜態工廠方法的注意。 在此期間,您能夠經過引發對類或接口文檔中的靜態工廠的注意並遵照常見的命名約定來減小此問題。 如下是靜態工廠方法的一些經常使用名稱。 這份清單遠非詳盡無遺:
Date d = Date.from(instant);
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object newArray = Array.newInstance(classObject, arrayLen);
FileStore fs = Files.getFileStore(path);
BufferedReader br = Files.newBufferedReader(path);
List<Complaint> litany = Collections.list(legacyLitany);
總之,靜態工廠方法和公共構造函數都有它們的用途,理解它們的相對優勢是值得的。 一般靜態工廠是優選的,不要在第一反應就是使用構造函數,應當先考慮使用靜態工廠方法。
關注公衆號獲取同步更新