參考於《Effective Java》
前言
對於類而言,獲得其實例最常見的方式就是提供一個構造器。
如果我們不寫構造器,編譯器會幫我們自動加上一個被public修飾的空構造器。
除了提供構造器以外,靜態工廠方法也應該被考慮到程序的設計當中。
靜態工廠方法
本質上就是類的一個靜態方法,返回值是類的實例對象。
通過私有化構造器,無法直接new對象,而是通過運行靜態工廠方法獲取對象實例。
這麼做既有優勢,也有劣勢。應該結合實際情況來使用。
優勢
1、靜態工廠方法有名稱
我們知道,類的構造器名稱必須和類名相同。
如果類比較複雜,需要多個構造器時,往往只能在構造器的方法簽名上做文章。
通過不同的參數個數,參數類型,參數順序來編寫多個構造器不是個好主意。
面對這樣的API,調用者往往不知所云,到底該調用哪一個構造器。
靜態工廠方法可以很好的規避這一點,通過不同的方法名來區分,調用者就會一目瞭然。
例子
@Data
public class Person {
private static enum Sex{
MAN,WOMAN
}
private String name;
private Sex sex;
private Person(){}
public static Person createMan(){
Person person = new Person();
person.sex = Sex.MAN;
return person;
}
public static Person createWoman(){
Person person = new Person();
person.sex = Sex.MAN;
return person;
}
}
2、不必在每次調用時都創建新對象
使用構造器創建對象,每次都會返回一個新的對象。
靜態工廠方法使得對象的創建是可控的。
可以將構建好的實例緩存起來進行重複利用,從而避免創建不必要的重複對象。
如果創建對象的代價很高,可以利用這項技術極大地提升性能。
Boolean.valueOf()很好的說明了這點。
它從不創建對象,返回的永遠是自身的TRUE,FALSE常量。
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
還有Integer.valueOf()採用了緩存的機制。
如果數值在 IntegerCache.low 和 IntegerCache.high 之間將從緩存中返回。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
3、可以返回原返回類型的任意子類
構造器返回的只能是本類的對象實例。
但是靜態工廠方法可以返回原返回類型的任意子類,這讓我們選擇返回類實例時大大的提高了靈活性。
當原先的類不能滿足需求,需要替換類時,只需要修改靜態工廠方法,而這一切對於調用者是零感知的。
public class Parent {
public void func(){
System.out.println("Parent Function.");
}
public static Parent getInstance(){
//返回任意子類實例
return new Child();
}
}
public class Child extends Parent {
@Override
public void func() {
super.func();
System.out.println("Child Enhanced Version.");
}
}
4、返回的對象的類可以隨着每次調用而發生變化
比起構造器只能返回該類本身,靜態工廠方法顯得靈活的多。
其完全是我們可控的,只要是已聲明的子類型都是允許的。
返回的類對象可能隨着發行版本的不同而不同。
5、返回實例的類可以在編寫方法時不存在
返回的子類可以在編寫靜態工廠方法時不存在,依賴於JDK提供的SPI機制,可以在需要時通過配置讓JVM發現服務並加載。
可以先編寫工廠方法,沒有實現類也無所謂。
public interface SPIInterface {
void func();
}
public class SPI {
public static SPIInterface getInstance(){
ServiceLoader<SPIInterface> serviceLoader = ServiceLoader.load(SPIInterface.class);
Iterator<SPIInterface> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
return iterator.next();
}
throw new RuntimeException("沒有可用的實現類");
}
public static void main(String[] args) {
SPIInterface instance = SPI.getInstance();
instance.func();
}
}
當需要提供服務時,按照SPI的規範來進行配置即可。
創建實現類
public class SPIImpl implements SPIInterface {
@Override
public void func() {
System.out.println("SPI 實現方法....");
}
}
SPI配置
在resources下新建文件:META-INF/services/unit1.item1.SPIInterface
,內容爲:
unit1.item1.SPIImpl
重新運行上面的代碼即可執行服務。
劣勢
1、不能被子類化
構造器私有化後,該類將無法被子類化。
2、較難以發現
相對於傳統的new方式創造對象,靜態工廠方式較難以被發現。
而且在JavaDoc生成的文檔中也沒有像構造器一樣被明確標識出來。
但是可以通過標準的命名習慣來彌補這一不足。
靜態工廠方法命名規範
-
form
類型轉換方法,它只有單個參數,返回該類型的一個相對應的實例。
Dated= Date.from(instant) ; -
of
聚合方法,帶有多個參數,返回該類型的一個實例,把它們合併起來。
Set faceCards = EnumSet.of(JACK , QUEEN, KING]; -
valueOf
比 from 和 of 更煩瑣的一種替代方法。
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE); -
instance/getInstance
返回的實例是通過方法的(如有)參數來描述的,但是不能說與參數具有同樣的值。
StackWalke luke = StackWalke.getinstance(options); -
create/newInstance
像instance/getInstance一樣,但create/newInstance能夠確保每次調用都返回一個新的實例。
Object newArray = Array.newInstance(classObject,arrayLen); -
getType
像getInstance一樣,但是在工廠方法處於不同的類中的時候使用。
FileStore fs = Files.getFileStore(path); -
newType
像newInstance一樣,但是在工廠方法處於不同的類中的時候使用。
BufferedReader br = Files.newBufferedReader(path); -
type
getType和newType的精簡版。
List litany = Collections.list(legacylLtany);