1. 程式人生 > >EffectiveJava讀書筆記- 第1條:考慮用靜態工廠方法代替構造器

EffectiveJava讀書筆記- 第1條:考慮用靜態工廠方法代替構造器

考慮靜態工廠方法代替構造器

靜態工廠方法相對於構造器的好處:

1. 靜態工廠方法有名字,相比構造器建立的物件更語義化

最好的例子就是併發庫中的Executors工具類了。

Executors中有多個建立執行緒池的方法:

public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
public static ExecutorService newSingleThreadExecutor
(); public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory); public static ExecutorService newCachedThreadPool(); public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory);

我們都知道這幾個方法最終建立的物件都是ThreadPoolExecutor物件。

而通過這幾個靜態工廠方法的方法名我們就已經大概知道建立了一個什麼樣的執行緒池。所以將來開發的過程中多考慮使用這種語義明確的靜態工廠方法吧。

2. 不必在每次呼叫的時候都建立一個新物件

書上舉了Boolean.valueOf(boolean)這個例子,還說這種方式類似於享元模式。其實個人比較喜歡把享元模式叫做物件池模式,這種設計模式通常用來儲存“建立起來比較耗時或耗資源”的大物件,比如資料庫連線池以及上面剛說到的執行緒池。Boolean中快取的TRUE,FALSE並不是什麼大物件,但卻是我們經常使用到的小物件,為了避免頻繁建立和GC回收物件,Boolean類就儲存了這兩個物件的引用。除了Boolean這個包裝類,Byte、Short、Integer、Long中都對[-128,127]區間內的物件進行了快取。

但是我覺得下面這個例子更能貼合我們的實際開發:

Charset.forName(String charsetName)

Charset是java.nio.charset包下面的類,這個方法是用於根據字符集的名字建立Charset物件的。

這個靜態工廠方法做了一個很簡單但又很複雜的LRU快取:這個快取使用兩個陣列儲存兩組鍵值對,key為charsetName字串,value為Charset物件。

當然我在這裡舉這個例子並不是想說這個快取演算法有多麼的高明,只是覺得我們可以用合適的快取演算法來避免重複的建立物件,同時避免物件一直佔據記憶體不得回收。要想簡單的實現一個物件快取,可以使用JDK自帶的LinkedHashMap或WeakHashMap兩個類,前者基於LRU演算法,後者基於GC回收演算法。

3. 可以返回返回型別的任何子類的物件

這個就不用多說了,前面的提到的Executors類中還有兩個java8新增的幾個靜態工廠方法:

public static ExecutorService newWorkStealingPool();
public static ExecutorService newWorkStealingPool(int parallelism);

這兩個方法返回的是ForkJoinPool型別的執行緒池。

書中舉的例子是java.util.EnumSet,EnumSet顧名思義:集合內的元素是列舉型別。

根據具體的列舉型別,可以得到列舉類中的所有列舉值,進一步就確定這個集合最大的容量了。EnumSet就直接把所有列舉值放到一個數組,然後通過類似於BitSet的點陣圖演算法並藉助列舉類值的ordinal作為索引來標記集合中是否有對應的列舉值。按照列舉類的大小它分成了兩種實現,列舉值個數小於64的直接用一個long進行標記,這就是RegularEnumSet的實現;列舉值個數大於64的,則用long[]進行標記,這就是JumboEnumSet的實現。

書中還提到了Java中經常見到的SPI機制,為此我還專門寫了一個示例來使用java.util.ServiceLoader類來載入SPI服務實現,書中說的SPI就是基於工廠方法實現的,而ServiceLoader則是使用反射以及META-INF/services/目錄下的檔案約定實現的。

4. 在建立引數化例項時,使程式碼變得更加簡潔

其實這個優點算不上是優點,所以書上說的為HashMap提供靜態工廠也遲遲在JDK中沒有落實。

不過在Java9中已經有了這些靜態工廠方法。

靜態工廠方法的缺點:

1. 如果將類的構造器私有化,那麼這個類就不能子類化

正如書中所說,不能子類化本質上是好處:鼓勵使用複合,而不是繼承

2. 與其他靜態方法沒有實質上的區別

靜態工廠方法本質上就是靜態方法,如果不在文件中宣告你可能並不知道這是一個靜態工廠方法。所以在命名靜態工廠方法的時候需要遵守一定的命名習慣,這些“習慣”其實就是JDK中大多數靜態工廠方法命名的方式:

  • valueOf:嚴格的說,這個方法應該是型別轉換;可以參考JDK中基本型別的包裝型別。
  • of:valueOf的簡潔版,可以參考EnumSet的實現。
  • getInstance:返回的示例物件是根據引數建立的,這個靜態方法也常被被用於單例模式。
  • newInstance:和getInstance一樣,但是newInstance返回的每個例項物件都是新建立。
  • getXxx:Xxx是類名。和getInstance一樣。
  • newXxx:Xxx是類名。和newInstance一樣。