The King's Museum

ソフトウェアエンジニアのブログ。

【Effective Java】項目3:private のコンストラクタか enum 型でシングルトン特性を強制する

シングルトンは、手短にいえば厳密に一度しかインスタンスが生成されないクラス。 ただし、クラスをシングルトンにするとデメリットも多々あるため本当に必要な場合のみ行うこと。

伝統的なシングルトン実装

Java の リリース 1.5 より前はシングルトンを実装する方法は二つ。

//
// public final フィールドによるシングルトン
//
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}

    public void someMethod() {}
}

//
// static ファクトリーメソッドによるシングルトン
//
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }
    public void someMethod() {}
}

もし、リフレクションによるインスタンス化も防ぎたい場合には、コンストラクタから例外を発生させる必要がある。

前者の利点はそのクラスがシングルトンであることが明白となっている点。 なお、パフォーマンス上の利点はない。 後者の方法であっても最新の JVM ではほぼ確実に inline 化される。

後者の利点はいくつかある。

  • static メソッドを経由しているので実装の変更が容易。
  • 例えば、現時点では単一のインスタンスを返すようになっているが、スレッドごとに別々のインスタンスを返すような変更ができる。
  • ジェネリクス型に関係する利点
    • 項目27で詳細に解説されるらしい…

ただ、大抵の場合、後者の利点は求められていないので、public final フィールドによる実装が単純である。

enum シングルトン

リリース 1.5 からはシングルトンを実装するよりよい方法がある。単一要素を持つ enum 型を作る、という方法だ。

public enum Elvis {
    INSTANCE;
    public void someMethod() {}
}

前述した伝統的なシングルトンをシリアライズ可能にするためには implements Serializable は不十分。 シングルトンを保証するためにはすべてのフィールドを transient と宣言して readResolve メソッドを提供する必要がある。 これを行わないとデシリアライズするごとに新たなインスタンスが生成されてしまう。

enum シングルトンは機能的には public フィールドによるシングルトンと同じなのだが、より完結で、何もしなくてもシリアライズ化機構を備えている。 シリアライズ攻撃やリフレクション攻撃に直面しても複数インスタンス生成を阻止する。

まだ広く採用されていないが、最善の方法とのこと。

感想

2015 年になるが、未だに enum シングルトンって見たことないな。利点があるのかネットで調べてみるか。

=> 調べてみた。 日本語で Google 検索すると大抵この Effective Java の項目3のまとめ記事(ちょうどこの記事みたいな)みたいなのばっかりで手法の是非についてはあまり議論されていなかった。 英語で Google 検索しても似たような感じ。まぁ、Java の巨匠が言ってるんだから間違いはないはずなので、機会があったら使おうかな。

あと、JVM ではインライン化されるものも、Dalvik だとされないことも多々あるよみたいな話を会社で聞いたけど、どうなんだろうな。これを調べるには自分の根性が足りん…

(c) The King's Museum