歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Effective Java - 用靜態工廠方法代替構造器

Effective Java - 用靜態工廠方法代替構造器

日期:2017/3/1 9:34:39   编辑:Linux編程

Effective Item - 考慮用靜態工廠方法代替構造器我們有兩種常見的方法獲得一個類的實例:

  • 公有的構造器
  • 提供靜態工廠方法(static factory method)

相對公有的構造器,靜態工廠方法有以下幾大優勢。

優勢1.靜態工廠方法的名稱,因此比構造器更准確地描述返回的實例。
比如BigInteger.probablePrime方法:

public static BigInteger probablePrime(int bitLength, Random rnd) {
    if (bitLength < 2)
        throw new ArithmeticException("bitLength < 2");

    // The cutoff of 95 was chosen empirically for best performance
    return (bitLength < SMALL_PRIME_THRESHOLD ?
            smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
            largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
}

順便也貼出其調用的largePrime方法:

private static BigInteger largePrime(int bitLength, int certainty, Random rnd) {
    BigInteger p;
    p = new BigInteger(bitLength, rnd).setBit(bitLength-1);
    p.mag[p.mag.length-1] &= 0xfffffffe;

    // Use a sieve length likely to contain the next prime number
    int searchLen = (bitLength / 20) * 64;
    BitSieve searchSieve = new BitSieve(p, searchLen);
    BigInteger candidate = searchSieve.retrieve(p, certainty, rnd);

    while ((candidate == null) || (candidate.bitLength() != bitLength)) {
        p = p.add(BigInteger.valueOf(2*searchLen));
        if (p.bitLength() != bitLength)
            p = new BigInteger(bitLength, rnd).setBit(bitLength-1);
        p.mag[p.mag.length-1] &= 0xfffffffe;
        searchSieve = new BitSieve(p, searchLen);
        candidate = searchSieve.retrieve(p, certainty, rnd);
    }
    return candidate;
}

雖然smallPrime和largePrime最後都是通過公有構造器返回實例。
但是如果僅僅用構造器重載表達這個實例的特征,這很難讓人記住什麼時候應該調用什麼構造器。
而提供一個名稱去描述實例更為直觀。

優勢2.靜態工廠方法不必每次都創建一個新的對象,我們可以對實例進行控制。
這樣我們就能將創建好的實例緩存起來重復利用,尤其是在創建對象的代價較高的情況下。
比如Boolean.valueOf:

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);
}

優勢3.靜態工廠方法可以返回原返回類型的子類型對象。
這一點能體現靜態工廠方法的靈活性,
以EnumSet為例:

/**
 * Creates an empty enum set with the specified element type.
 *
 * @param elementType the class object of the element type for this enum
 *     set
 * @throws NullPointerException if <tt>elementType</tt> is null
 */
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");

    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

而RegularEnumSet和JumboEnumSet為EnumSet的子類,並且都沒有提供公有構造器。

優勢4.靜態工廠方法創建參數化(泛型)實例的時候更加簡潔。
舉個例子:

public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
}


這樣一來創建實例時就可以:

Map<String,List<Integer>> n = newInstance();

而不是

Map<String,List<Integer>> m = new HashMap<String,List<Integer>>();

從Java7開始這一點變得沒有意義,事實上Josh Bloch也在書上提到了這點——Java以後會在構造器和方法調用中執行這種類型推導。

說說靜態工廠方法的缺點。

  • 類如果不含公有或者受保護的構造器就不能被子類化。
    所以上面說的靜態工廠方法可以返回原返回類型的子類型對象。並不完全正確。
    雖然我們可以通過復合方式(composition)解決這一問題,但這樣兩個類就不是is-a關系了。
  • 靜態工廠方法的本質還是靜態方法,他沒有一個特別的標准。
    我們無法在API文檔中把一個靜態工廠方法特別標識出來(也許可以加個標准annotation?)。
    當我要從API中找一個方法去實例化一個類時,相對構造器而言還是不夠直觀。
    雖然沒有特個特別的標准,但我們也可以用標准的命名來彌補一點點。
    比如valueOf,getInstance,newInstance,newType等...

Copyright © Linux教程網 All Rights Reserved