這是Effective Java第2章提出的第一條建議:
考慮用靜態工廠方法代替構造器
此處的靜態工廠方法並不是設計模式,主要指static修飾的靜態方法,關於static的說明可以參考之前的博文《java中final與static的使用場景總結》。
什么是靜態工廠方法?
可以參考書中的例子(摘自JDK1.7 java.lang.Boolean)
public final class Boolean implements java.io.Serializable,
Comparable<Boolean> { public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } }
如果需要獲取一個Boolean對象,常規的方法是new Boolean(true)
,但是也可以如上圖所示Boolean.valueOf(true)
,這便是靜態工廠方法。
靜態工廠方法的優勢
靜態工廠方法與構造器不同的第一大優勢在於,它們有名稱。
使用構造函數構造對象時,我們需要通過文檔仔細比對傳遞什么樣的參數能夠構造什么樣的對象。但是靜態工廠方法可以使用不同的方法名字使得其構造的對象更加明晰。我們完全可以通過方法名明白構造了什么樣的對象。
例如下面的例子(摘自JDK1.7 java.math.BigInteger)
public class BigInteger extends Number implements Comparable<BigInteger> {
/**
* Returns a positive BigInteger that is probably prime, with the
* specified bitLength. The probability that a BigInteger returned
* by this method is composite does not exceed 2<sup>-100</sup>.
*/
public static BigInteger probablePrime(int bitLength, Random rnd) { // XXX } }
靜態工廠方法與構造器不同的第二大優勢在於,不必在每次調用它們的時候都創建一個新對象。
我們調用靜態工廠方法返回的可能是緩存的一個對象,而不是新對象。可以進行重復利用,從而避免創建不必要的重復對象。
如果程序經常請求創建相同的對象,並且創建的代價很高,則靜態工廠方法可以極大地提升性能。
前面提到的Boolean.valueOf(boolean)
便說明了這項技術。
靜態工廠方法與構造器不同的第三大優勢在於,他們可以返回原返回類型的任何子類型的對象。
我們在選擇返回對象的類時有了更大的靈活性。參見java.util.EnumSet,其本身被abstract修飾,無法直接調用其構造函數。但可以調用其靜態方法noneOf
來創建對象,並且根據參數返回合適的對象。
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable {
EnumSet(Class<E>elementType, Enum[] universe) { } //RegularEnumSet與JumboEnumSet均為EnumSet的子類 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); } }
第四大優勢在於,創建參數化類型實例的時候,可以使代碼變得更加簡潔。
例如對於HashMap
的實例化:
//常規實例化方式
Map<String, List<String>> m =
new HashMap<String, List<String>>();
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); } //使用靜態工廠方法實例化,簡化繁瑣的聲明 Map<String, List<String>> m = HashMap.newInstance();
靜態工廠方法的劣勢
類如果不含公有的或受保護的構造器,就不能被實例化。
如果我們在類中將構造函數設為private,只提供靜態工廠方法來構建對象,那么我們將不能通過繼承擴展該類。
但是這也會鼓勵我們使用復合而不是繼承來擴展類。
它們與其他的靜態方法實際上沒有任何區別。
在API文檔中,構建對象的靜態工廠方法並沒有像構造器那樣明確標識出來,不能和其他靜態方法很方便地區分開來。
如果類中只提供靜態工廠方法而不是構造器,要想查明如何實例化一個類將會變得困難。
我們可以通過遵循靜態工廠方法的命名規范來彌補這一劣勢:
- valueOf - 返回的實例與它的參數具有相同的值,一般作為類型轉換使用,例如
Boolean.valueOf(boolean)
- of - valueOf的更為簡潔的替代。
- getInstance - 返回的實例通過方法的參數來描述,但不能說與參數具有同樣的值。對於Singleton來說,使用無參getInstance,返回唯一的實例。
- newInstance - 像getInstance一樣,但其能夠確保每次都返回新的對象。
- getType - 像getInstance一樣,但此方法返回的對象是另一個不同的類。
- newType - 像getType一樣,但每次返回一個新對象。
總而言之,靜態工廠方法和公有構造器具有各自的用處,但靜態工廠方法通常更加合適,所以我們應該優先考慮靜態工廠方法。