Effective Java に書かれている通り、 Java でシングルトンを作りたい場合は単一要素の enum
を作成するという方法があります。そんでまぁ結構気軽に enum
を使っていたんですが、コンストラクタで例外が発生しうるような実装にしてしまうと辛いということに気づきました。
例えばこんな enum
を作って、
public enum SampleEnum { INSTANCE; private SampleEnum() { throw new RuntimeException("enumコンストラクタで例外"); } public void doHoge() { System.out.println("doHoge"); } }
こんな感じで使ってみるとします。
public static void main(String[] args) { try { System.out.println("main"); SampleEnum.INSTANCE.doHoge(); } catch (Exception ex) { System.err.println("nice catch!"); } }
実行すると以下の様な出力になります。
main Exception in thread "main" java.lang.ExceptionInInitializerError at sample.App.main(App.java:40) Caused by: java.lang.RuntimeException: enumコンストラクタで例外 at sample.SampleEnum.<init>(SampleEnum.java:14) at sample.SampleEnum.<clinit>(SampleEnum.java:11) ... 1 more
catch
節に入ってないですね。これは enum
のコンストラクタでスローした例外が ExceptionInInitializerError
に変換*1されており、 Exception
の catch
では捕捉できないためです。 java.lang.Error
をキャッチすれば捕捉可能ですが、エラーは一般的に JVM がヤバい感じの時にスローされるものなので、キャッチすべきではなく*2、通常エラーのキャッチ節を書くことも無いと思います。
これの何が辛いって、通常のアプリケーション例外処理に乗らないことです。例えば上記のように例外をキャッチしてログ出力している場合、エラーなのでキャッチできず意図したログが出ません。また、 enum
のコンストラクタ内で try/catch
しても、コンストラクタ内では static
なロガーを参照できないのでやはりログを出力できません。
そもそも enum
にかぎらずコンストラクタから例外投げるのはどうなのか、という話もありますが、こと enum
に関しては特に扱いが厄介になります。 enum
のコンストラクタから例外が発生しうるような実装は避けたほうが良いです。
enum
のコンストラクタで例外が発生する実装ってどんなんやねん、という話ですが、 enum
に final
フィールドを生やしてコンストラクタで外部ファイルから値を読み込んで設定する、みたいな作りとかですかね。アプリケーションスコープな値オブジェクトとして使えるかなと思ったんですが、読み込みに失敗した場合辛いということがわかりました。こういう場合はアプリケーションの初期化処理とかで明示的に読み込み処理を呼ぶような作りにしたほうが無難かなと思います。