あるクラスのインスタンスが1つしか存在しないようにして,また外部からアクセスできるようにする.
インスタンスを複数生成させないために留意すべき点は以下の通り
- newさせない
- コンストラクタはprivateに
- インスタンスはファクトリメソッドなどを利用して取得
- 遅延初期化したい
- 複数スレッドからのアクセスを想定
- デシリアライズさせない
- readResolveを利用
- クローンさせない
以上を守れば普通は問題ないが,よりセキュアにするために以下のような観点もあるようだ
- GCに回収させない(他のClassLoaderを利用させない)
- リフレクションで作成させない
間違えやすいので後述のenumを使ったほうが良い.
この例は機能をテンコ盛りだが,必要な部分だけ実装すればOK
class Singleton extends CloneableClass implements Serializable {
// フィールドは全てtransientに
private transient String field;
// 外部のクラスからコンストラクタにアクセスさせない
private Singleton() {
// 初期化
}
// Initialize-on-Demand Holderパターンによる遅延初期化クラス
private static class SingletonHolder {
static Singleton singleton = new Singleton();
Singleton getInstance() {
return singleton;
}
}
// ファクトリメソッド
public static Singleton getInstance() {
return SingletonHolder.getInstance();
}
// デシリアライズの結果を上書き
private Object readResolve() throws ObjectStreamException {
return getInstance();
}
// クローンさせない
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
enumを利用すると(特にserialize周りで)面倒なことをVMに任せられる
enum Singleton { // enumは既にSerializable
INSTANCE;
// transientにしなくてよい
private String field;
}
ただし,この方法はインターフェースは実装できるがクラスを継承できない
Double-checked lockingの話はやめよう