159

ウィキペディアでシングルトンの記事を読んでいたところ、次の例に出くわしました。

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;
    }
}

この Singleton の動作はとても気に入っていますが、引数をコンストラクターに組み込むためにそれを適応させる方法がわかりません。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;
    }
}

ありがとう!


編集: 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).
    }
}

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

4

21 に答える 21

34

インスタンス化と取得を分離するために、構成可能な初期化メソッドを追加できます。

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)て構成できます。

于 2016-09-27T18:01:57.477 に答える
14

一部のパラメーターが必須であることを示したい場合は、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);
        }

    }


}

次に、次のように作成/インスタンス化/パラメーター化できます。

public static void main(String[] args) {
    new EnumSingleton.SingletonBuilder("nico").age(41).build();
    EnumSingleton.getSingleton().print();
}
于 2014-10-06T13:54:18.563 に答える
9

ロガーの作成/取得方法について誰も言及していないことに驚きました。たとえば、以下は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)

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

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      } 
...
于 2015-10-17T23:56:46.467 に答える
6

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

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

デザイン パターンは、柔軟で拡張可能なコードを書くのに役立つものであり、優れたコードを書くことを妨げるものではありません。

于 2016-04-19T11:30:02.650 に答える
4

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);
    }
}
于 2010-08-22T23:05:53.750 に答える
4

Context として機能する Singleton クラスを作成する場合、良い方法は、構成ファイルを用意し、instance() 内のファイルからパラメーターを読み取ることです。

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

于 2017-06-11T10:39:30.107 に答える
4

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

Singleton.getInstance().setX(value);
于 2009-06-26T20:07:30.013 に答える
3

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

于 2010-10-06T15:25:43.610 に答える
3

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

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

getInstance(2)一方、別のオブジェクトを返したい場合はgetInstance(5)、Singleton パターンを使用しておらず、Factory パターンを使用しています。

于 2009-06-26T20:07:56.393 に答える
1

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

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;
    }
}
于 2015-12-04T12:38:30.057 に答える
1

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

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;
  }
}
于 2014-11-28T21:25:33.987 に答える
0

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

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

于 2015-03-08T16:14:49.463 に答える
-4

通常、シングルトンはアンチパターンと見なされるため、使用しないでください。コードを簡単にテストできるわけではありません。

とにかく、引数を持つシングルトンは意味がありません-次のように書いたらどうなるでしょうか:

Singleton s = SingletonHolder.getInstance(1);
Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException

複数のスレッドが を同時に呼び出すことができるため、シングルトンもスレッドセーフではありませんgetInstancex

于 2009-06-26T20:05:30.440 に答える