以下のようなSingleton
を使ってJavaでEnum
を実装することが可能であることを読んだことがあります。
public enum MySingleton {
INSTANCE;
}
しかし、上記はどのように機能するのでしょうか。具体的には、Object
をインスタンス化する必要があります。ここで、MySingleton
はどのようにインスタンス化されていますか?誰がnew MySingleton()
をしていますか?
この、
public enum MySingleton {
INSTANCE;
}
暗黙的に空のコンストラクタがあります。代わりに明示的にしてください。
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
その後、次のようなmain()
メソッドを使って別のクラスを追加したとします。
public static void main(String[] args) {
System.out.println(MySingleton.INSTANCE);
}
なるほど
Here
INSTANCE
enum
フィールドはコンパイル時定数ですが、それらはそれらのenum
型のインスタンスです。そして、それらはenum型が初めてで参照されるときに構築されます。
enum
型はclass
の特別な型です。
あなたのenum
は実際には以下のようにコンパイルされます。
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
コードが最初にINSTANCE
にアクセスすると、クラスMySingleton
がJVMによってロードおよび初期化されます。このプロセスは、上記のstatic
フィールドを初期化しますonce(lazyily)。
Joshua Blochによる Javaベストプラクティスブック には、なぜシングルトンプロパティをプライベートコンストラクタまたはEnum型で強制する必要があるのかが説明されています。章はかなり長いので、要約しておきます。
クラスをシングルトンにすると、その型として機能するインタフェースを実装しない限り、シングルトンの代わりにモック実装を使用することは不可能になるため、クライアントのテストが困難になる可能性があります。推奨される方法は、単純に1つの要素を持つ列挙型を作成することによってシングルトンを実装することです。
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
このアプローチは、より簡潔で、シリアル化の仕組みを無料で提供し、洗練されたシリアル化やリフレクション攻撃に直面したとしても、多重インスタンス化に対する確固たる保証を提供することを除いて、パブリックフィールドアプローチと機能的に同等です。
このアプローチはまだ広く採用されていませんが、単一要素のenum型がシングルトンを実装するための最良の方法です。
すべての列挙型インスタンスと同様に、Javaはクラスがロードされるときに各オブジェクトをインスタンス化します。ただし、 JVM ごとに1回インスタンス化されることが保証されています。 INSTANCE
宣言をpublic static finalフィールドと考えてください。クラスが最初に参照されるときに、Javaはオブジェクトをインスタンス化します。
インスタンスは静的初期化中に作成されます。静的初期化は Java言語仕様、セクション12.4 で定義されています。
その価値があるので、 Joshua Bloch ではこのパターンについて詳しく説明しています Effective Java Second Edition 。
Singleton Patternはプライベートコンストラクタを持つこととインスタンス化を制御するためのメソッドを呼び出すこと(getInstance
のように)であるので、Enumには既に暗黙のプライベートコンストラクタがあります。
JVMまたは一部のcontainerがEnums
のインスタンスをどのように制御するのか、正確にはわかりませんが、暗黙のSingleton Pattern
がすでに使用されているようですが、違いは違います。 getInstance
を呼び出さないで、Enumを呼び出すだけです。
ある程度前に述べたように、enumはその定義が少なくとも1つの "enum定数"で始まらなければならないという特別な条件を持つJavaクラスです。
それとは別に、これは他のクラスと同じクラスであり、定数定義の下にメソッドを追加して使用します。
public enum MySingleton {
INSTANCE;
public void doSomething() { ... }
public synchronized String getSomething() { return something; }
private String something;
}
これらの行に沿ってシングルトンのメソッドにアクセスします。
MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();
クラスの代わりにenumを使用することは、他の回答で述べたように、ほとんどシングルトンのスレッドセーフなインスタンス化と、それが常に1つのコピーになることの保証についてです。
そして、おそらく最も重要なのは、この動作がJVM自体とJava仕様によって保証されていることです。
Enumインスタンスの複数のインスタンスがどのようにして防止されるかについての Java specification からのセクションです。
列挙型には、その列挙定数によって定義されたもの以外のインスタンスはありません。列挙型を明示的にインスタンス化しようとするのはコンパイル時エラーです。 Enumの最後のcloneメソッドはenum定数が決してクローンされないことを保証します、そして直列化メカニズムによる特別な取り扱いは重複インスタンスがデシリアライゼーションの結果として決して作成されないことを保証します。列挙型の反射的インスタンス化は禁止されています。まとめると、これら4つのことは、enum定数で定義されたもの以外にenum型のインスタンスが存在しないことを保証します。
注目に値するのは、インスタンス化の後、スレッドの安全性に関する懸念は、他のクラスと同様にsynchronizedキーワードなどで処理される必要があるということです。