第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統一、規範了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等。