带有Java中的参数的单例
我正在阅读Wikipedia上的Singleton文章,并且遇到了这个例子:
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; } }
谢谢!
编辑:我想我已经开始了一个争议的风暴,我希望使用辛格尔顿。 让我解释一下我的动机,希望有人能提出一个更好的主意。 我正在使用网格计算框架来并行执行任务。 一般来说,我有这样的东西:
// 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). } }
正如你所看到的,即使在这里,我有问题,传递一个不同的文件path意味着第一个传递后没有任何意义。 这就是为什么我喜欢在答案中发布商店的想法。 无论如何,我不想在run方法中包含加载文件的逻辑,我想把这个逻辑抽象成一个Singleton类。 我不会再提供一个例子,但是我希望你明白。 请让我听听你的想法,以更优雅的方式来完成我想要做的事情。 再次感谢你!
我会清楚地说明一点: 带参数的单身人士不是单身人士 。
按照定义,一个单例是一个你想要实例化的对象不超过一次。 如果你正试图给构造函数提供参数,那么这个单例又是什么意思呢?
你有两个select。 如果你想让你的单例初始化一些数据,你可以在实例化之后加载数据,如下所示:
SingletonObj singleton = SingletonObj.getInstance(); singleton.init(paramA, paramB); // init the object with data
如果你的单例正在执行的操作是反复出现的,并且每次使用不同的参数,那么你也可以将parameter passing给正在执行的主要方法:
SingletonObj singleton = SingletonObj.getInstance(); singleton.doSomething(paramA, paramB); // pass parameters on execution
无论如何,实例化将始终是无参数的。 否则你的单身人士不是一个单身人士。
我认为你需要像工厂这样的东西,让各种参数的对象被实例化和重用。 它可以通过使用一个同步的HashMap
或ConcurrentHashMap
将一个参数(一个Integer
作为例子)映射到你的'singleton'参数化类来实现。
尽pipe你可能会使用常规的非单例类(例如需要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 enum
也可以被认为(或用作)参数化的单例,尽pipe只允许固定数量的静态variables。
但是,如果您需要分布式解决scheme,请考虑一些横向caching解决scheme。 例如:EHCache,兵马俑等
1是在多个计算机上跨越多个虚拟机的意义上说的。
如果您想要显示某些参数是强制性的,您也可以使用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); } } }
然后你可以创build/实例化/参数化如下:
public static void main(String[] args) { new EnumSingleton.SingletonBuilder("nico").age(41).build(); EnumSingleton.getSingleton().print(); }
使用getter和setter来设置variables,并使默认构造函数为私有。 然后使用:
Singleton.getInstance().setX(value);
惊讶的是没有人提到如何创build/检索logging器。 例如,下面显示了如何检索Log4Jlogging器 。
// 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)
有一些间接的级别,但关键部分是下面的方法 ,几乎告诉一切有关它的工作原理。 它使用一个散列表来存储退出的logging器,并且该键是从名字派生的。 如果logging器对于给定名称不存在,则使用工厂创buildlogging器,然后将其添加到散列表。
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 } ...
使用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); } }
在你的例子中,你没有使用单例。 请注意,如果您执行以下操作(假设Singleton.getInstance实际上是静态的):
Singleton obj1 = Singleton.getInstance(3); Singleton obj2 = Singleton.getInstance(4);
那么obj2.x的值是3,而不是4.如果你需要这样做,使它成为一个普通的类。 如果值的数量很小并且是固定的,则可以考虑使用enum
。 如果你有过多的对象生成问题(通常不是这种情况),那么你可以考虑caching值(并检查源代码或获得帮助,因为显然如何构buildcaching而没有内存泄漏的危险)。
你也可能想阅读这篇文章,因为单身人士可能会很容易被滥用。
有一个关于单身人士的坏的和好的讨论(有很多好的链接)在这里 – http://c2.com/cgi/wiki?SingletonsAreEvil (不要让标题欺骗你 – 这是相当公正的)
我build议使用dependency injection来控制你的应用程序的组成,而不是像工厂和单身的结构模式。
Singletons是反模式的另一个原因是,如果按照build议书写,使用私有构造函数,它们很难在某些unit testing中进行子类化和configuration使用。 例如,在维护遗留代码时将需要。
你无法理解如何完成你想要做的事情的原因可能是你想要做的事情没有意义。 你想用不同的参数调用getInstance(x)
,但总是返回相同的对象? 当你调用getInstance(2)
和getInstance(5)
时,你想要什么样的行为?
如果你想要相同的对象,但内部的价值是不同的,这是唯一的办法,它仍然是一个单身人士,那么你根本不需要关心构造函数; 你只需在getInstance()
中设置对象出来的值即可。 当然,你明白所有你对单身人士的其他引用现在有不同的内在价值。
如果您希望getInstance(2)
和getInstance(5)
返回不同的对象,则不使用Singleton模式,而使用Factory模式。
您可以添加一个初始化方法来分隔获取的实例。
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)
来configuration它,例如在你的应用程序启动中。
难道我们不能做这样的事情:
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; } }
“ 带参数的单身人士不是单身人士 ”的陈述并不完全正确 。 我们需要从应用程序的angular度来分析,而不是从代码的angular度来分析。
我们构build单例类来在一个应用程序运行中创build一个对象的单个实例。 通过使用带参数的构造函数,可以在代码中增加灵活性,以便在每次运行应用程序时更改单例对象的某些属性。 这并不违反Singleton模式。 如果你从代码的angular度来看,这看起来像是一个违规行为。
devise模式可以帮助我们编写灵活和可扩展的代码,而不是阻碍我们编写好的代码。
这不是一个单身人士,但可能是可以解决您的问题的东西。
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; } }
如果我们把这个问题看作“如何使状态变成单态”,那么就不需要把状态作为构造参数。 我同意在获取单例实例后初始化状态或使用set方法的post。
另一个问题是:有单身的状态是好事吗?
我刚刚遇到同样的问题,我相信我find了解决scheme……
这是没有解决办法。 我想解释一下为什么先分析这个问题。
正如读者可能理解的那样,singleton类是通过声明构造函数为private来实现的,然后以静态方法实例化。 以下是最基本的实现。 原谅我的C风格括号放置。
public class Singleton { private instance = new Singleton(); private Singleton() { } public static getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
由于我们的目标是在实例化过程中将parameter passing给instance
,参数必须被构造函数接受。 然后,因为实例化只能发生在一个静态方法中(在这种情况下是getInstance()
),所以当调用getInstance()
时,参数必须由Singleton
类保存。 由于在调用getInstance()
之前,我们无法控制其他方法的调用,所以将parameter passing给Singleton
的唯一方法是在类初始化期间传递参数。
因此,在这一点上问题变成了“在类初始化时如何将parameter passing给类”。 而且通过一些快速的研究,我发现这是不可能的。 正如对另一个问题的回答指出的那样, static{}
块是Java的类初始化器,并且“ 不支持任何参数 ”。 所以使用三段论链,斯科特问题的最终答案是这是不可能的。
就我个人而言,我认为这个问题从根本上是由于Java缺乏对单例类的官方支持而造成的。 你在上面看到的实现有点像程序员的“贫民区临时替代者”,所以有一些局限性也不足为奇。 我很高兴看到Java在下一个主要版本中正式包括Singleton
类,当然,它(和它的子类一样)只能被实例化一次,否则抛出一个运行时exceptionMultipleInstantiationException
。
Ps我曾经想过使用types声明(就像你将如何做new ArrayList<someType>()
)从另一个容器类的静态字段获得参数,但是这只是将问题改变为“如何确保一个用户在实例化类B
之前从类A
调用方法“。 所以它并不是真正的工作,如果真的这样做的话,它仍然是一个非常糟糕的解决scheme。
Pss我也试图dynamic地添加getInstance()
到Singleton
只有在调用另一个parameter passing方法。 然而正如在这个问题中讨论的人们一样,Java不像C或者Python,并不真正喜欢它的程序员这样做。
尽pipe有些人可能会断言,但这里是一个在构造函数中有参数的单例
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 */ }
单身模式说:
- 确保只有单例类的一个实例存在
- 提供对该实例的全局访问权限。
这是受这个例子的尊重。
为什么不直接设置属性? 这是教科书的情况下,我们可以得到一个具有参数构造函数的单例,但在某些情况下它可能是有用的。 例如在inheritance的情况下强制单身人士设置一些超类属性。
如果你想创build一个作为Context的Singleton类,一个好的方法是创build一个configuration文件并从instance()中的文件中读取参数。
如果在程序运行期间dynamic获取提供给Singleton类的参数,只需使用在Singleton类中存储不同实例的静态HashMap来确保每个参数只创build一个实例。
当然,Singleton是一个“反模式”(假定一个静态variables状态的定义)。
如果你想要一个固定的一组不变值对象,那么枚举就是要走的路。 对于一个大的,可能是开放式的值集合,你可以使用某种forms的Repository(通常基于Map
实现)。 当然,当你正在处理静态的时候要小心线程(要么足够广泛的ConcurrentMap
要么使用ConcurrentMap
来检查另一个线程没有被打败或者使用某种forms的未来)。
单身人士通常被认为是反模式 ,不应该使用。 他们不会使代码易于testing。
有一个论点的单身人士无论如何也没有意义 – 如果你写道:
Singleton s = SingletonHolder.getInstance(1); Singleton t = SingletonHolder.getInstance(2); //should probably throw IllegalStateException
您的单例也不是线程安全的,因为多个线程可以同时调用getInstance
导致创build多个实例(可能具有不同的x
值)。