web-dev-qa-db-ja.com

Javaに引数を持つシングルトン

私はウィキペディアのシングルトンの記事を読んでいて、この例を見つけました。

public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

私はこのシングルトンの振る舞いが本当に好きですが、コンストラクターに引数を組み込むためにそれをどのように適合させるかわかりません。 Javaでこれを行うための好ましい方法は何ですか?このようなことをする必要がありますか?

public class Singleton
{
    private static Singleton singleton = null;  
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public synchronized static Singleton getInstance(int x) {
        if(singleton == null) singleton = new Singleton(x);
        return singleton;
    }
}

ありがとう!


編集:私はシングルトンを使用したいという私の欲求と論争の嵐を開始したと思います。私の動機を説明させてください、そして誰かがより良いアイデアを提案できることを願っています。グリッドコンピューティングフレームワークを使用して、タスクを並行して実行しています。一般的に、私はこのようなものを持っています:

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private final ReferenceToReallyBigObject object;

    public Task(ReferenceToReallyBigObject object)
    {
        this.object = object;
    }

    public void run()
    {
        // Do some stuff with the object (which is immutable).
    }
}

発生するのは、データへの参照をすべてのタスクに渡すだけでも、タスクがシリアル化されると、データが繰り返しコピーされるということです。私がやりたいのは、すべてのタスク間でオブジェクトを共有することです。当然、次のようにクラスを変更できます。

// AbstractTask implements Serializable
public class Task extends AbstractTask
{
    private static ReferenceToReallyBigObject object = null;

    private final String filePath;

    public Task(String filePath)
    {
        this.filePath = filePath;
    }

    public void run()
    {
        synchronized(this)
        {
            if(object == null)
            {
                ObjectReader reader = new ObjectReader(filePath);
                object = reader.read();
            }
        }

        // Do some stuff with the object (which is immutable).
    }
}

ご覧のとおり、ここでも、最初のパスが渡された後、別のファイルパスを渡しても意味がないという問題があります。これが、回答に投稿されたstoreのアイデアが好きな理由です。とにかく、ファイルをロードするためのロジックをrunメソッドに含めるのではなく、このロジックをシングルトンクラスに抽象化したかったのです。私はまだ別の例を提供しませんが、私はあなたがアイデアを得ることを望みます。私がやろうとしていることを達成するためのよりエレガントな方法のためのあなたのアイデアを聞かせてください。ありがとうございました!

128
Scott

パラメーターを持つシングルトンはシングルトンではありません

シングルトンとは、定義上、インスタンス化を1回しか行わないオブジェクトです。パラメーターをコンストラクターにフィードしようとしている場合、シングルトンのポイントは何ですか?

2つのオプションがあります。シングルトンを何らかのデータで初期化する場合、インスタンス化後にデータをロードできます

SingletonObj singleton = SingletonObj.getInstance();
singleton.init(paramA, paramB); // init the object with data

シングルトンが実行している操作が繰り返し実行され、毎回異なるパラメーターを使用する場合は、実行されるメインメソッドにパラメーターを渡すこともできます。

SingletonObj singleton = SingletonObj.getInstance();
singleton.doSomething(paramA, paramB); // pass parameters on execution

いずれにせよ、インスタンス化は常にパラメーターなしです。それ以外の場合、シングルトンはシングルトンではありません。

160
Yuval Adam

さまざまなパラメータを持つオブジェクトをインスタンス化して再利用するには、factoryのようなものが必要だと思います。同期されたHashMapまたはConcurrentHashMapを使用して実装できます。パラメータ(たとえば、Integer)を「シングルトン」パラメータ化可能クラスにマップします。

ただし、代わりに通常の非シングルトンクラスを使用する必要がある場合があります(たとえば、10,000個の異なるパラメータ化されたシングルトンが必要です)。

そのようなストアの例を次に示します。

public final class UsefulObjFactory {

    private static Map<Integer, UsefulObj> store =
        new HashMap<Integer, UsefulObj>();

    public static final class UsefulObj {
        private UsefulObj(int parameter) {
            // init
        }
        public void someUsefulMethod() {
            // some useful operation
        }
    }

    public static UsefulObj get(int parameter) {
        synchronized (store) {
            UsefulObj result = store.get(parameter);
            if (result == null) {
                result = new UsefulObj(parameter);
                store.put(parameter, result);
            }
            return result;
        }
    }
}

さらにプッシュするには、Java enumsをパラメーター化されたシングルトンと見なす(または使用する)こともできますが、固定数の静的バリアントのみを許可します。

ただし、分散が必要な場合1 ソリューション、いくつかのラテラルキャッシュソリューションを検討してください。例:EHCache、Terracottaなど。

1 おそらく複数のコンピューター上の複数のVMにまたがるという意味で。

39
akarnokd

一部のパラメーターが必須であることを示す場合は、Builderパターンを使用することもできます。

    public enum EnumSingleton {

    INSTANCE;

    private String name; // Mandatory
    private Double age = null; // Not Mandatory

    private void build(SingletonBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
    }

    // Static getter
    public static EnumSingleton getSingleton() {
        return INSTANCE;
    }

    public void print() {
        System.out.println("Name "+name + ", age: "+age);
    }


    public static class SingletonBuilder {

        private final String name; // Mandatory
        private Double age = null; // Not Mandatory

        private SingletonBuilder(){
          name = null;
        }

        SingletonBuilder(String name) {
            this.name = name;
        }

        public SingletonBuilder age(double age) {
            this.age = age;
            return this;
        }

        public void build(){
            EnumSingleton.INSTANCE.build(this);
        }

    }


}

次に、create/instantiate/parametrized次のようにします。

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}
10
gerardnico

インスタンス化を取得から分離するために、初期化メソッドを追加できます。

public class Singleton {
    private static Singleton singleton = null;
    private final int x;

    private Singleton(int x) {
        this.x = x;
    }

    public static Singleton getInstance() {
        if(singleton == null) {
            throw new AssertionError("You have to call init first");
        }

        return singleton;
    }

    public synchronized static Singleton init(int x) {
        if (singleton != null)
        {
            // in my opinion this is optional, but for the purists it ensures
            // that you only ever get the same instance when you call getInstance
            throw new AssertionError("You already initialized me");
        }

        singleton = new Singleton(x);
        return singleton;
    }

}

次に、たとえばアプリの起動時に、Singleton.init(123)を1回呼び出して設定します。

10
miguel

ゲッターとセッターを使用して変数を設定し、デフォルトのコンストラクターをプライベートにします。次に使用します:

Singleton.getInstance().setX(value);
7
AlbertoPL

"パラメータを持つシングルトンはシングルトンではありません"ステートメントは完全に正しくないです。コードの観点からではなく、アプリケーションの観点からこれを分析する必要があります。

シングルトンクラスを構築して、1回のアプリケーション実行でオブジェクトの単一インスタンスを作成します。パラメーターを持つコンストラクターを使用すると、コードを柔軟に構築して、アプリケーションを実行するたびにシングルトンオブジェクトの一部の属性を変更できます。これはシングルトンパターンの違反ではありません。これをコードの観点から見ると、違反のように見えます。

デザインパターンは、優れたコードの作成を妨げるのではなく、柔軟で拡張可能なコードを作成するのに役立ちます。

5
Vinod Nalla

ロガーの作成/取得方法について誰も言及していないことに驚いた。たとえば、以下は Log4Jロガー の取得方法を示しています。

// Retrieve a logger named according to the value of the name parameter. If the named logger already exists, then the existing instance will be returned. Otherwise, a new instance is created.
public static Logger getLogger(String name)

インダイレクションにはいくつかのレベルがありますが、重要な部分は method の下にあります。ハッシュテーブルを使用して既存のロガーを保存し、キーは名前から派生します。与えられた名前のロガーが存在しない場合は、ファクトリを使用してロガーを作成し、それをハッシュテーブルに追加します。

69   Hashtable ht;
...
258  public
259  Logger getLogger(String name, LoggerFactory factory) {
260    //System.out.println("getInstance("+name+") called.");
261    CategoryKey key = new CategoryKey(name);
262    // Synchronize to prevent write conflicts. Read conflicts (in
263    // getChainedLevel method) are possible only if variable
264    // assignments are non-atomic.
265    Logger logger;
266
267    synchronized(ht) {
268      Object o = ht.get(key);
269      if(o == null) {
270        logger = factory.makeNewLoggerInstance(name);
271        logger.setHierarchy(this);
272        ht.put(key, logger);
273        updateParents(logger);
274        return logger;
275      } else if(o instanceof Logger) {
276        return (Logger) o;
277      } 
...
5
wanghq

Bill Pughのオンデマンドホルダーイディオムでの初期化 を使用するシングルトンパターンの変更。これは、特殊な言語構造(つまり、揮発性または同期化)のオーバーヘッドなしでスレッドセーフです。

public final class RInterfaceHL {

    /**
     * Private constructor prevents instantiation from other classes.
     */
    private RInterfaceHL() { }

    /**
     * R REPL (read-evaluate-parse loop) handler.
     */
    private static RMainLoopCallbacks rloopHandler = null;

    /**
     * SingletonHolder is loaded, and the static initializer executed, 
     * on the first execution of Singleton.getInstance() or the first 
     * access to SingletonHolder.INSTANCE, not before.
     */
    private static final class SingletonHolder {

        /**
         * Singleton instance, with static initializer.
         */
        private static final RInterfaceHL INSTANCE = initRInterfaceHL();

        /**
         * Initialize RInterfaceHL singleton instance using rLoopHandler from
         * outer class.
         * 
         * @return RInterfaceHL instance
         */
        private static RInterfaceHL initRInterfaceHL() {
            try {
                return new RInterfaceHL(rloopHandler);
            } catch (REngineException e) {
                // a static initializer cannot throw exceptions
                // but it can throw an ExceptionInInitializerError
                throw new ExceptionInInitializerError(e);
            }
        }

        /**
         * Prevent instantiation.
         */
        private SingletonHolder() {
        }

        /**
         * Get singleton RInterfaceHL.
         * 
         * @return RInterfaceHL singleton.
         */
        public static RInterfaceHL getInstance() {
            return SingletonHolder.INSTANCE;
        }

    }

    /**
     * Return the singleton instance of RInterfaceHL. Only the first call to
     * this will establish the rloopHandler.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @return RInterfaceHL singleton instance
     * @throws REngineException
     *             if REngine cannot be created
     */
    public static RInterfaceHL getInstance(RMainLoopCallbacks rloopHandler)
            throws REngineException {
        RInterfaceHL.rloopHandler = rloopHandler;

        RInterfaceHL instance = null;

        try {
            instance = SingletonHolder.getInstance();
        } catch (ExceptionInInitializerError e) {

            // rethrow exception that occurred in the initializer
            // so our caller can deal with it
            Throwable exceptionInInit = e.getCause();
            throw new REngineException(null, exceptionInInit.getMessage());
        }

        return instance;
    }

    /**
     * org.rosuda.REngine.REngine high level R interface.
     */
    private REngine rosudaEngine = null;

    /**
     * Construct new RInterfaceHL. Only ever gets called once by
     * {@link SingletonHolder.initRInterfaceHL}.
     * 
     * @param rloopHandler
     *            R REPL handler supplied by client.
     * @throws REngineException
     *             if R cannot be loaded.
     */
    private RInterfaceHL(RMainLoopCallbacks rloopHandler)
            throws REngineException {

        // tell Rengine code not to die if it can't
        // load the JRI native DLLs. This allows
        // us to catch the UnsatisfiedLinkError
        // ourselves
        System.setProperty("jri.ignore.ule", "yes");

        rosudaEngine = new JRIEngine(new String[] { "--no-save" }, rloopHandler);
    }
}
4
tekumara

シングルトンがアンチパターンであるもう1つの理由は、プライベートコンストラクタを使用して推奨事項に従って記述した場合、特定の単体テストで使用するためにサブクラス化および構成するのが非常に難しいことです。たとえば、レガシーコードを維持する場合に必要になります。

3
JosefB

あなたがしようとしていることを達成する方法を理解できない理由は、おそらくあなたがしようとしていることは本当に意味をなさないからです。 getInstance(x)を異なる引数で呼び出したいが、常に同じオブジェクトを返しますか? getInstance(2)を呼び出してからgetInstance(5)を呼び出すときにどのような動作が必要ですか?

同じオブジェクトが必要であるが、その内部値が異なる場合、これはまだシングルトンである唯一の方法である場合、コンストラクターをまったく気にする必要はありません。オブジェクトの出口でgetInstance()に値を設定するだけです。もちろん、他のすべてのシングルトンへの参照には現在、異なる内部値があることを理解しています。

getInstance(2)getInstance(5)が異なるオブジェクトを返すようにする場合、一方、Singletonパターンを使用していない場合は、Factoryパターンを使用しています。

3
chaos

あなたの例では、シングルトンを使用していません。以下を実行することに注意してください(Singleton.getInstanceが実際に静的であると仮定):

Singleton obj1 = Singleton.getInstance(3);
Singleton obj2 = Singleton.getInstance(4);

Obj2.xの値は4ではなく3です。これを行う必要がある場合は、プレーンクラスにします。値の数が小さく固定されている場合は、enumの使用を検討できます。過剰なオブジェクト生成の問題がある場合(通常はそうではありません)、値のキャッシュを検討できます(そして、メモリリークの危険なしにキャッシュを構築する方法が明らかなので、ソースを確認するか、それについて助けを求めます)。

また、シングルトンは非常に簡単に使いすぎてしまう可能性があるため、 この記事を読む をお勧めします。

3
Kathy Van Stone

コンテキストとして機能するシングルトンクラスを作成する場合、設定ファイルを用意し、instance()内のファイルからパラメーターを読み取ることをお勧めします。

プログラムの実行中にシングルトンクラスに供給するパラメーターが動的に取得される場合は、シングルトンクラスに異なるインスタンスを格納する静的HashMapを使用して、各パラメーターに対して1つのインスタンスのみが作成されるようにします。

2
user3025839

このようなことはできませんでした:

public class Singleton {

    private int x;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
     * or the first access to SingletonHolder.INSTANCE, not before.
     */
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance(int x) {
        Singleton instance = SingletonHolder.INSTANCE;
        instance.x = x;
        return instance;
    }
}
1
Ionut Negru

問題を「状態でシングルトンを作成する方法」と見なす場合、コンストラクターパラメーターとして状態を渡す必要はありません。状態を初期化する投稿、またはシングルトンインスタンスを取得した後にsetメソッドを使用する投稿に同意します。

別の質問は次のとおりです。状態を持つシングルトンを持つことは良いことですか?

1
user3014901

いくつかの主張がありますが、コンストラクターにパラメーターを持つシングルトンがあります

public class Singleton {

    private static String aParameterStored;

    private static final Singleton instance = new Singleton("Param to set");

    private Singleton() {
        // do nothing
    }

    private Singleton(String param) {
        aParameterStored = param;
    }

    public static Singleton getInstance() {
        return instance;
    }

    /*
     * ... stuff you would like the singleton do
     */
}

シングルトンパターンは言う:

  • シングルトンクラスのインスタンスが1つだけ存在することを確認する
  • そのインスタンスへのグローバルアクセスを提供します。

この例では尊重されます。

なぜプロパティを直接設定しないのですか?パラメータ付きのコンストラクタを持つシングルトンを取得する方法を示すのは教科書のケースですが、状況によっては役立つ場合があります。たとえば、継承の場合、シングルトンにいくつかのスーパークラスプロパティを設定させる。

1
Zou

これはまったくシングルトンではありませんが、問題を解決できる可能性があります。

public class KamilManager {

  private static KamilManager sharedInstance;

  /**
   * This method cannot be called before calling KamilManager constructor or else
   * it will bomb out.
   * @return
   */
  public static KamilManager getInstanceAfterInitialized() {
    if(sharedInstance == null)
        throw new RuntimeException("You must instantiate KamilManager once, before calling this method");

    return sharedInstance;
}

  public KamilManager(Context context, KamilConfig KamilConfig) {
    //Set whatever you need to set here then call:
  s  haredInstance = this;
  }
}
0
Kamilski81

これを答えとして投稿するのは怖いですが、なぜこれについて誰も考えないのか理解できません。

public class example  {
    private volatile static example instance;

    private String string;
    private int iInt = -1; //any number you know you don't want to use here

  private example() {

    //In case someone uses the private method to create a new Instance
    if (instance != null){
      throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
  }

  public synchronized static example getIsntance(){
    if(instance == null){
      instance = new example();
    }
    return instance;
  }

public void methodDoingWork(){
    if(checkInit()){
      //DoSome
    }
  }

  private boolean checkInit(){
    boolean filled = (this.string != null) && (this.iInt != -1);
    return filled;
  }

  public void setString(String string) {
    if(this.string == null){
      this.string = string;
    }else{
      throw new RuntimeException("You try to override an already setValue"); 
    }
  }

  public void setiInt(int iInt) {
    if(this.iInt == -1){
      this.iInt = iInt;
    }else{
      throw new RuntimeException("You try to override an already setValue");
    }
  }
}

getInstance()は毎回同じインスタンスを返すので、これはうまくいくと思います。これが間違っている場合は削除しますが、このトピックに興味があります。

0
HydroHeiperGen