天天看點

枚舉的構造函數中抛出異常會怎樣

首先從使用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,而且參數合法性非常重要,那可以考慮在構造函數中檢查參數并抛出合适的異常。