Java 中带参数的单例

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1050991/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:45:54  来源:igfitidea点击:

Singleton with Arguments in Java

javaoopsingletonanti-patterns

提问by

I was reading the Singleton article on Wikipedia and I came across this example:

我正在阅读维基百科上的单例文章,我遇到了这个例子:

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

While I really like the way this Singleton behaves, I can't see how to adapt it to incorporate arguments to the constructor. What is the preferred way to do this in Java? Would I have to do something like this?

虽然我真的很喜欢这个 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;
    }
}

Thanks!

谢谢!



Edit: I think I have started a storm of controversy with my desire to use Singleton. Let me explain my motivation and hopefully someone can suggest a better idea. I am using a grid computing framework to execute tasks in parallel. In general, I have something like this:

编辑:我想我已经开始了一场争议风暴,因为我希望使用 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).
    }
}

What happens is that even though I merely pass a reference to my data to all of the tasks, when the tasks are serialized, the data gets copied over and over. What I want to do is share the object among all of the tasks. Naturally, I might modify the class like so:

发生的情况是,即使我只是将我的数据的引用传递给所有任务,但当任务被序列化时,数据会一遍又一遍地复制。我想做的是在所有任务之间共享对象。当然,我可能会像这样修改类:

// 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).
    }
}

As you can see, even here I have the issue that passing a different file path means nothing after the first one is passed. This is why I like the idea for a storewhich was posted in the answers. Anyhow, rather than including the logic for loading the file in the run method, I wanted to abstract this logic into a Singleton class. I will not provide yet another example, but I hope you get the idea. Please let me hear your ideas for a more elegant way to accomplish what I am trying to do. Thank you again!

如您所见,即使在这里我也遇到了一个问题,即在传递第一个文件路径后传递不同的文件路径没有任何意义。这就是为什么我喜欢在答案中发布的商店的想法。无论如何,我不想在 run 方法中包含加载文件的逻辑,而是想将此逻辑抽象为一个 Singleton 类。我不会再提供另一个例子,但我希望你能明白。请让我听听您的想法,以更优雅的方式来完成我正在尝试做的事情。再次感谢你!

回答by oxbow_lakes

Singletons are generally considered to be anti-patternsand shouldn't be used. They do not make code easy to test.

单例通常被认为是反模式,不应使用。它们不会使代码易于测试。

A singleton with an argument makes no sense anyway - what would happen if you wrote:

无论如何,带有参数的单身人士毫无意义-如果您编写以下内容会发生什么:

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

Your singleton is also not thread-safeas multiple threads can make simultaneous calls to getInstanceresulting in more than one instance being created (possibly with different values of x).

您的单例也不是线程安全的,因为多个线程可以同时调用getInstance导致创建多个实例(可能具有不同的 值x)。

回答by AlbertoPL

Use getters and setters to set the variable and make the default constructor private. Then use:

使用 getter 和 setter 来设置变量并将默认构造函数设为私有。然后使用:

Singleton.getInstance().setX(value);

回答by chaos

The reason you can't make sense of how to accomplish what you're trying to do is probably that what you're trying to do doesn't really make sense. You want to call getInstance(x)with different arguments, but always return the same object? What behavior is it you want when you call getInstance(2)and then getInstance(5)?

您无法理解如何完成您正在尝试做的事情的原因可能是您正在尝试做的事情并没有真正的意义。你想getInstance(x)用不同的参数调用,但总是返回相同的对象?当你打电话getInstance(2)然后你想要什么行为getInstance(5)

If you want the same object but for its internal value to be different, which is the only way it's still a singleton, then you don't need to care about the constructor at all; you just set the value in getInstance()on the object's way out. Of course, you understand that all your other references to the singleton now have a different internal value.

如果你想要同一个对象但它的内部值不同,这是它仍然是单例的唯一方式,那么你根本不需要关心构造函数;您只需getInstance()在对象的出路中设置值。当然,您知道对单例的所有其他引用现在都具有不同的内部值。

If you want getInstance(2)and getInstance(5)to return different objects, on the other hand, you're not using the Singleton pattern, you're using the Factory pattern.

如果你想getInstance(2)getInstance(5)返回不同的对象,在另一方面,你不使用Singleton模式,您使用的工厂模式。

回答by akarnokd

I think you need something like a factoryto have objects with various parameters instantiated and reused. It could be implemented by using a synchronized HashMapor ConcurrentHashMapmap a parameter (an Integerfor an example) to your 'singleton' parameterizable class.

我认为您需要像工厂这样的东西来实例化和重用具有各种参数的对象。它可以通过使用同步HashMapConcurrentHashMap映射参数(Integer例如)到您的“单例”可参数化类来实现。

Although you might get to the point where you should use regular, non-singleton classes instead (for example needing 10.000 differently parametrized singleton).

尽管您可能会到了应该使用常规的非单例类的地步(例如,需要 10.000 个不同参数化的单例)。

Here is an example for such store:

以下是此类商店的示例:

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

To push it even further, the Java enums can be also considered (or used as) parametrized singletons, although allowing only a fixed number static variants.

为了进一步推动它,Javaenum也可以被视为(或用作)参数化单例,尽管只允许固定数量的静态变体。

However, if you need a distributed1solution, consider some lateral caching solution. For example: EHCache, Terracotta, etc.

但是,如果您需要分布式1解决方案,请考虑一些横向缓存解决方案。例如:EHCache、Terracotta 等。

1in the sense of spanning multiple VMs on probably multiple computers.

1在可能跨越多台计算机上的多个 VM 的意义上。

回答by Kathy Van Stone

In your example you are not using a singleton. Notice that if you do the following (assuming that the Singleton.getInstance was actually static):

在您的示例中,您没有使用单例。请注意,如果您执行以下操作(假设 Singleton.getInstance 实际上是静态的):

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

Then the obj2.x's values is 3, not 4. If you need to do this, make it a plain class. If the number of values is small and fixed, you can consider using an enum. If you are having problem with excessive object generation (which is usually not the case), then you can consider caching values (and check sources or get help with that, as it is obvious how to build caches without the danger of memory leaks).

那么 obj2.x 的值为 3,而不是 4。如果您需要这样做,请将其设为普通类。如果值的数量很小且固定,则可以考虑使用enum. 如果您遇到过多对象生成的问题(通常不是这种情况),那么您可以考虑缓存值(并检查源或获得帮助,因为很明显如何在没有内存泄漏危险的情况下构建缓存)。

You also might want to read this articleas singletons can be very easily overused.

您可能还想阅读这篇文章,因为单例很容易被过度使用。

回答by Tom Hawtin - tackline

Singleton is, of course, an "anti-pattern" (assuming a definition of a static with variable state).

单例当然是一种“反模式”(假设定义了具有可变状态的静态)。

If you want a fixed set of immutable value objects, then enums are the way to go. For a large, possibly open-ended set of values, you can use a Repository of some form - usually based on a Mapimplementation. Of course, when you are dealing with statics be careful with threading (either synchronise sufficiently widely or use a ConcurrentMapeither checking that another thread hasn't beaten you or use some form of futures).

如果您想要一组固定的不可变值对象,那么枚举是最佳选择。对于大型的、可能是开放式的值集,您可以使用某种形式的存储库 - 通常基于Map实现。当然,当您处理静态时要小心线程(要么足够广泛地同步,要么使用ConcurrentMap检查另一个线程没有击败您或使用某种形式的期货)。

回答by Yuval Adam

I'll make my point very clear: a singleton with parameters is not a singleton.

我会非常清楚地说明我的观点:带参数的单例不是单例

A singleton, by definition, is an object you want to be instantiated no more than once. If you are trying to feed parameters to the constructor, what is the point of the singleton?

单例,顾名思义,是一个你想被实例化不超过一次的对象。如果您尝试向构造函数提供参数,那么单例的意义何在?

You have two options. If you want your singleton to be initialized with some data, you may load it with data after instantiation, like so:

你有两个选择。如果你想让你的单例用一些数据初始化,你可以在实例化之后用数据加载它,像这样:

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

If the operation your singleton is performing is recurring, and with different parameters every time, you might as well pass the parameters to the main method being executed:

如果你的单例正在执行的操作是重复的,并且每次都有不同的参数,你不妨将参数传递给正在执行的 main 方法:

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

In any case, instantiation will always be parameter-less. Otherwise your singleton is not a singleton.

在任何情况下,实例化总是无参数的。否则你的单身人士不是单身人士。

回答by tekumara

Modification of Singleton pattern that uses Bill Pugh's initialization on demand holder idiom. This is thread safe without the overhead of specialized language constructs (i.e. volatile or synchronized):

使用Bill Pugh 的按需初始化的单例模式的修改。这是线程安全的,没有专门的语言结构(即 volatile 或 synchronized)的开销:

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

回答by JosefB

Another reason Singletons are an anti-pattern is that if written according to recommendations, with private constructor, they are very hard to subclass and configure to use in certain unit tests. Would be required in maintaining legacy code, for example.

单例是反模式的另一个原因是,如果根据建议编写,使用私有构造函数,它们很难被子类化和配置以在某些单元测试中使用。例如,在维护遗留代码时需要。

回答by gerardnico

You can also use the Builder pattern if you want to show that some parameters are mandatory.

如果您想显示某些参数是必需的,您也可以使用 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);
        }

    }


}

Then you could create/instantiate/parametrizedit as follow:

然后你可以创建/实例化/参数化它如下:

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