EffectiveJava_創建和銷燬對象_靜態工廠方法的使用

第1條、考慮用靜態工廠方法替代構造器

在基本類型的包裝類中,如Boolean存在靜態工廠方法:

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;    
}

考慮靜態工廠方法的優勢:

  • 靜態工廠方法有自己的名稱,而構造器則只能與類名相同。

對於有多個構造器的類,他們的不同只在於參數列表,對於這樣的構造器用戶經常不知道該調用哪個,從而會造成很多的錯誤。而靜態工廠方法的好處是可以有自己的名稱,用戶從名稱上就可以讀懂這個構造器是構造什麼樣的實例。

  • 靜態工廠方法可避免創建不必要的重複對象。

如上面提到的Boolean類,它從不創建對象,這種方法類似於享元模式。

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

    /**
     * Returns a {@code Boolean} instance representing the specified
     * {@code boolean} value.  If the specified {@code boolean} value
     * is {@code true}, this method returns {@code Boolean.TRUE};
     * if it is {@code false}, this method returns {@code Boolean.FALSE}.
     * If a new {@code Boolean} instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Boolean(boolean)}, as this method is likely to yield
     * significantly better space and time performance.
     *
     * @param  b a boolean value.
     * @return a {@code Boolean} instance representing {@code b}.
     * @since  1.4
     */
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    /**
     * Returns a {@code Boolean} with a value represented by the
     * specified string.  The {@code Boolean} returned represents a
     * true value if the string argument is not {@code null}
     * and is equal, ignoring case, to the string {@code "true"}.
     *
     * @param   s   a string.
     * @return  the {@code Boolean} value represented by the string.
     */
    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

}
  • 靜態工廠方法可以返回原返回類型的任何子類型的對象,而構造器只能構造本類型對象。

這是一種靈活性的應用,API可以返回對象,同時又不會使對象的類變成公有的。例如 java.util.Collections 提供了不同的靜態工廠方法,返回對象的類都是非公有的,例如不可修改的集合、同步集合等等,這樣的接口有32個。

對於這樣做的好處,書中是這麼寫的:“現在的Collections Framework API比導出32個獨立公有類的那種實現方式要小得多,每種便利實現都對應一個類。這不僅僅是指API數量上的減少,也是概念意義上的減少。”個人對這句話的理解是:每一個公有類對應着相應的文檔供用戶使用時閱讀,但是對於不可變集合、同步集合這種只是在性質上發生改變而內部API等並無變化,操作上與普通集合一致的類並沒有必要設計爲獨立的公有類。(用戶只需瞭解它是不可變的,並不關心內部實現。)

public class Collections {
    // Suppresses default constructor, ensuring non-instantiability.
    //私有構造器,意味着該類不能實例化。
    private Collections() {
    }
    public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
        return new UnmodifiableCollection<>(c);
    }

    /**
     * @serial include
     */
    //其中的一種便利實現,不可變的集合
    //default的可見性,意味着包內可見。如果在包外嘗試實例化,會編譯報錯。
    static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 1820017752578914078L;

        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
        }

        public int size()                   {return c.size();}
        public boolean isEmpty()            {return c.isEmpty();}
        public boolean contains(Object o)   {return c.contains(o);}
        public Object[] toArray()           {return c.toArray();}
        public <T> T[] toArray(T[] a)       {return c.toArray(a);}
        public String toString()            {return c.toString();}

        public Iterator<E> iterator() {
            return new Iterator<E>() {
                private final Iterator<? extends E> i = c.iterator();

                public boolean hasNext() {return i.hasNext();}
                public E next()          {return i.next();}
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    // Use backing collection version
                    i.forEachRemaining(action);
                }
            };
        }

        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

      ...
}

說到這裏好像並沒有對“返回子類型的對象”有什麼優勢做出解釋,更像是再說可以“減少公有類”的好處。個人理解:將靜態工廠方法的返回類型設計成父類,只要返回對象的類型是子類,就是允許的。這符合里氏代換原則,我們在選擇返回對象類型的時候就有更大的靈活性。另一方面,對於用戶來說,也不關心返回的是什麼類型的,完全可以又靜態工廠方法的參數去決定使用那種更具有優勢或者實際需要的子類。

這裏我們將提到服務提供者架構(Service Provider Framework),以數據庫連接操作作爲例子可能更好理解。(以下是部分關鍵類類圖)

JDBC部分類圖

我們都知道JDBC統一、規範了Java對不同數據的操作。也就是服務定義接口(java.sql.Connection)、服務提供者接口(java.sql.Driver)和提供服務者註冊類(java.sql.DriverManager)是由java統一制定的,而不同提供商只需要遵循這樣的同一的規定去實現服務具體實現類(com.mysql.cj.jdbc.ConnectionImp)和提供服務者(com.mysql.cj.jdbc.NonRegisteringDriver)實現類。這樣做的好處是:我們使用不同的數據庫進行鏈接是只需要註冊不同的驅動器,也就是Driver,就可以拿到正確的是數據庫連接。可以看到在NonRegisteringDriver中connect方法返回的是超類Connection,無論是mysql或是oracle都是適用的。

我們獲得Connection的操作通常是這樣的:

Class.forName("com.mysql.jdbc.Driver");
Connection connection= DriverManager.getConnection("jdbc:mysql:///mydatabase", "root", "root");

我們可以看一下com.mysql.jdbc.Driver中的代碼:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

很明顯當我們調用Class.forName(...)這個方法到時候,就已經觸發Driver中的靜態代碼塊,把Driver加載進入DriverManager中了。在這裏實際上在DriverManager的內部存在的是這樣一個屬性:

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

我們會發現這裏的註冊驅動器數組並不是Driver類型的,而是DriverInfo類型。查看以下源碼(在DriverManager的同一文件下):

/*
 * Wrapper class for registered Drivers in order to not expose Driver.equals()
 * to avoid the capture of the Driver it being compared to as it might not
 * normally have access.
 */
//註冊驅動程序的包裝類,以便不公開Driver.Equals(),以避免捕獲與之比較的驅動程序,因爲它通常不具有訪問權限。
class DriverInfo {

    final Driver driver;
    DriverAction da;
    DriverInfo(Driver driver, DriverAction action) {
        this.driver = driver;
        da = action;
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof DriverInfo)
                && this.driver == ((DriverInfo) other).driver;
    }

    @Override
    public int hashCode() {
        return driver.hashCode();
    }

    @Override
    public String toString() {
        return ("driver[className="  + driver + "]");
    }

    DriverAction action() {
        return da;
    }
}

這個類採用了複合的方式來擴展了Driver類,增加了DriverAction類,目前個人的理解是是用於註銷驅動器的時候使用的。

而我們可以關注的一點是,註釋中提到的equals方法。這在《Effective Java》第8條規則中將會被提到。

public static synchronized void deregisterDriver(Driver driver)
        throws SQLException {

    ...

                 // If a DriverAction was specified, Call it to notify the
                 // driver that it has been deregistered
                //如果指定了DriverAction,則調用它以通知驅動程序它已被註銷。 
                 if(di.action() != null) {
                     di.action().deregister();
                 }
                 registeredDrivers.remove(aDriver);
            } else {
                // If the caller does not have permission to load the driver then
                // throw a SecurityException.
                //如果調用方沒有加載驅動程序的權限,則拋出SecurityException。 
                throw new SecurityException();
            }
        } else {
            println("    couldn't find driver to unload");
        }
    }
  • 在創建參數化類型的時候,靜態工廠方法可以使代碼更加簡潔。
Map<String ,List<String>> m = new HashMap<String,List<String>>();

假設HashMap存在靜態工廠方法:

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

那麼上面冗長的代碼就可更換爲:

Map<String,List<String>> m = HashMap.newInstance();

這個例子在jdk1.6以前是成立的。目前個人使用的jdk1.7已經可以寫成:

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

關於靜態工廠方法的缺點:

  • 類如果不包含公有的或者受保護的構造器,就不能被子類化。

原因就是子類化的時候都會先調用父類的構造器。

  • 靜態工廠方法與其他靜態方法沒有區別。

在API文檔中,它們沒有像其他構造器那樣在API文檔中明確標識出來,因此對於提供靜態工廠方法而不是構造器的類,要查明如何實例化一個類,這是非常困難的。解決這個問題的一個方法就是遵守標準的命名習慣,如:

valueOf、getInstance、newInstance、getType、newTpye等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章