Comment créer une classe singleton
Quelle est la meilleure façon / correcte de créer une classe singleton en java?
L'une des implémentations que j'ai trouvées utilise un constructeur privé et une méthode getInstance ().
package singleton;
public class Singleton {
private static Singleton me;
private Singleton() {
}
public static Singleton getInstance() {
if (me == null) {
me = new Singleton();
}
return me;
}
}
Mais l'implémentation échoue-t-elle dans le cas de test suivant
package singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
/**
* @param args
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
* @throws IllegalArgumentException
*/
public static void main(String[] args) throws SecurityException,
NoSuchMethodException, IllegalArgumentException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
Singleton singleton1 = Singleton.getInstance();
System.out.println(singleton1);
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton2);
Constructor<Singleton> c = Singleton.class
.getDeclaredConstructor((Class<?>[]) null);
c.setAccessible(true);
System.out.println(c);
Singleton singleton3 = c.newInstance((Object[]) null);
System.out.println(singleton3);
if(singleton1 == singleton2){
System.out.println("Variable 1 and 2 referes same instance");
}else{
System.out.println("Variable 1 and 2 referes different instances");
}
if(singleton1 == singleton3){
System.out.println("Variable 1 and 3 referes same instance");
}else{
System.out.println("Variable 1 and 3 referes different instances");
}
}
}
Comment résoudre ce problème?
Merci
6 réponses
Selon le commentaire sur votre question:
j'ai un fichier de propriétés contenant des paires de valeurs de clés, ce qui est nécessaire dans l'application, c'est pourquoi je pensais à une classe singleton. Cette classe va charger les propriétés à partir d'un fichier et le conserver et vous pouvez l'utiliser depuis n'importe où dans l'application
N'utilisez pas de singleton. Vous n'avez apparemment pas besoin d'une initialisation unique lazy (c'est là qu'est un singleton). Vous voulez une seule fois initialisation directe . Il suffit de le rendre statique et de le charger dans un initialiseur statique.
Par exemple
public class Config {
private static final Properties PROPERTIES = new Properties();
static {
try {
PROPERTIES.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties"));
} catch (IOException e) {
throw new ExceptionInInitializerError("Loading config file failed.", e);
}
}
public static String getProperty(String key) {
return PROPERTIES.getProperty(key);
}
// ...
}
Si vous utilisez la réflexion pour percer l'encapsulation, vous ne devriez pas être surpris lorsque le comportement de votre classe est modifié de manière incorrecte. Les membres privés sont censés être privés à la classe. En utilisant la réflexion pour y accéder, vous cassez intentionnellement le comportement de la classe, et le "singleton en double" résultant est attendu.
En bref: ne faites pas ça.
En outre, vous pouvez envisager de créer l'instance singleton dans un constructeur statique. Statique les constructeurs sont synchronisés et ne s'exécuteront qu'une seule fois. Votre classe actuelle contient une condition de concurrence-si deux threads distincts appellent getInstance()
alors qu'il n'a pas été précédemment appelé, il est possible que deux instances soient créées, l'une étant exclusive à l'un des threads, et l'autre devenant l'instance que les futurs appels getInstance()
retourneront.
Je vais implémenter singleton de la manière ci-dessous.
De Singleton_pattern décrit par wikiepdia en utilisant idiome de support D'initialisation à la demande
Cette solution est thread-safe sans nécessiter de constructions de langage spéciales (c'est-à-dire volatile
ou synchronized
public final class LazySingleton {
private LazySingleton() {}
public static LazySingleton getInstance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final LazySingleton INSTANCE = new LazySingleton();
}
private Object readResolve() {
return LazyHolder.INSTANCE;
}
}
La meilleure façon de créer une classe Singleton en java est d'utiliser des Enums.
Exemple comme ci-dessous :
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
enum SingleInstance{
INSTANCE;
private SingleInstance() {
System.out.println("constructor");
}
}
public class EnumSingletonDemo {
public static void main (String args[]) throws FileNotFoundException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
SingleInstance s=SingleInstance.INSTANCE;
SingleInstance s1=SingleInstance.INSTANCE;
System.out.println(s.hashCode() + " "+s1.hashCode());//prints same hashcode indicates only one instance created
//------- Serialization -------
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("sample.ser"));
oos.writeObject(s);
oos.close();
//------- De-Serialization -------
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("sample.ser"));
SingleInstance s2=(SingleInstance) ois.readObject();
System.out.println("Serialization :: "+s.hashCode()+" "+s2.hashCode());// prints same hashcodes because JVM handles serialization in case of enum(we dont need to override readResolve() method)
//-----Accessing private enum constructor using Reflection-----
Class c=Class.forName("SingleInstance");
Constructor co=c.getDeclaredConstructor();//throws NoSuchMethodException
co.setAccessible(true);
SingleInstance newInst=(SingleInstance) co.newInstance();
}
}
NoSuchMethodException est lancé parce que nous ne pouvons pas créer une autre instance de enum 'SingleInstance' via son constructeur privé en utilisant la réflexion.
En cas de sérialisation, enum implémente l'interface sérialisable par défaut.
Je pense que vous pouvez vérifier si une instance existe déjà dans le constructeur et si existe lancer une exception
if(me != null){
throw new InstanceAlreadyExistsException();
}
Il suffit de suivre le diagramme de classe singleton pattern,
SingletonClass - singletonObject: SingletonClass - SingletonClass() + getObject (): SingletonClass
Point clé,
- privé votre constructeur
- l'instance de votre classe doit être dans la classe
- fournissez la fonction pour renvoyer votre instance
Certains codes,
public class SingletonClass {
private static boolean hasObject = false;
private static SingletonClass singletonObject = null;
public static SingletonClass getObject() {
if (hasObject) {
return singletonObject;
} else {
hasObject = true;
singletonObject = new SingletonClass();
return singletonObject;
}
}
private SingletonClass() {
// Initialize your object.
}
}