首先從使用enum實作單例說起。
為什麼要用enum來實作單例?
這篇文章([url]http://javarevisited.blogspot.sg/2012/07/why-enum-singleton-are-better-in-java.html[/url])闡述了三個理由:
1.enum單例簡單、容易,隻需幾行代碼:
2.enum單例自動處理序列化問題
傳統的單例實作方式(例如懶漢式餓漢式),如果它implements Serializable,那它就不再是單例了,因為readObject方法總是會傳回新的對象。
enum雖然implements Serializable,但它仍然是單例,這是由jvm保證的。
3.enum單例是線程安全的
此外,《Effective Java》也建議用enum實作單例,當然還有stackoverflow的讨論:[url]http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java[/url]
但是,[b]用enum實作單例的話,它的構造函數不能抛出異常[/b],否則會抛出Error(而不是Exception)。
試想這樣一種情況,在遠端調用中,服務端抛出了Error,而用戶端try-catch的是Exception,那就捕獲不到出錯資訊,用戶端就直接崩潰了。
說到遠端調用,說點題外話,dubbo當中是不建議傳遞枚舉的([url]http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-%E5%85%BC%E5%AE%B9%E6%80%A7[/url]):
測試代碼:
對三種單例實作的方式(枚舉、懶漢模式、餓漢模式)進行測試,發現隻有懶漢模式是抛出Exception,其它兩種都是抛出ExceptionInInitializerError。
這很好解釋,因為懶漢模式是在方法(getInstance)調用中出錯,而枚舉方式和餓漢方式都是在類加載(Class initialization)時出錯(The constructors are invoked when the enum class is initialized)。
類執行個體化出錯顯然更嚴重一些。
是以,在枚舉方式和餓漢方式實作單例時,注意不要讓構造函數抛出異常。
這就引申出第二個問題,在構造函數中要不要抛出異常呢?
《編寫高品質代碼-改善Java程式的151個建議》一書當中,作者在第114條建議中認為:不要在構造函數中抛出異常,盡管你可以這麼做:
1.抛出unchecked Exception
例如:
這也是比較常見的一種做法。
這個做法的問題是,調用者不知道是捕獲這個異常還是不捕獲。
捕獲吧,要看文檔或者源碼才知道會抛什麼異常,而且捕獲的代碼顯得非常難看:
不捕獲吧,出現IllegalArgumentException時,後續代碼就無法執行了。
2.抛出checked Exception
這種做法引起的主要問題是,子類的構造函數中也要抛出checked Exception
看看stackoverflow的讨論:
[url]http://stackoverflow.com/questions/6086334/is-it-good-practice-to-make-the-constructor-throw-an-exception[/url]
得票最高的看法是:
當參數不合法時,抛出異常是唯一的、合理的做法。但是要選擇合适的Exception,而不是直接抛出java.lang.Exception。
也有人認為,在構造函數中抛出異常是“壞的實踐”:你應該在傳遞參數給構造函數之前,檢查參數的合法性。
說法不一。
我認為還是按簡單的來處理,也就是不抛異常,把參數合法性的檢查交給調用方。例如平時代碼中我們寫得最多的當然是類似這樣的:
public class Person {
private int age;
public Person (int age) {
this.age = age;
}
}
沒有進行參數檢查。
如果某個類不是普通的java bean,而且參數合法性非常重要,那可以考慮在構造函數中檢查參數并抛出合适的異常。