Traditionnellement, un singleton est généralement implémenté comme
public class Foo1
{
private static final Foo1 INSTANCE = new Foo1();
public static Foo1 getInstance(){ return INSTANCE; }
private Foo1(){}
public void doo(){ ... }
}
Avec l'énumération de Java, nous pouvons implémenter un singleton comme
public enum Foo2
{
INSTANCE;
public void doo(){ ... }
}
Aussi génial que la 2ème version, y a-t-il des inconvénients?
(J'ai réfléchi et je répondrai à ma propre question; j'espère que vous aurez de meilleures réponses)
Réponses:
Quelques problèmes avec les singletons enum:
S'engager dans une stratégie de mise en œuvre
En général, "singleton" fait référence à une stratégie d'implémentation, et non à une spécification d'API. Il est très rare de
Foo1.getInstance()
déclarer publiquement qu'il retournera toujours la même instance. Si nécessaire, l'implémentation deFoo1.getInstance()
peut évoluer, par exemple, pour renvoyer une instance par thread.Avec
Foo2.INSTANCE
nous déclarons publiquement que cette instance est l' instance, et il n'y a aucune chance de changer cela. La stratégie de mise en œuvre consistant à avoir une seule instance est exposée et engagée.Ce problème n'est pas paralysant. Par exemple,
Foo2.INSTANCE.doo()
peut s'appuyer sur un objet d'assistance local de thread, pour avoir effectivement une instance par thread.Extension de la classe Enum
Foo2
étend une super classeEnum<Foo2>
. Nous voulons généralement éviter les super classes; surtout dans ce cas, la super classe forcéeFoo2
n'a rien à voir avec ce quiFoo2
est censé être. C'est une pollution de la hiérarchie des types de notre application. Si nous voulons vraiment une super classe, c'est généralement une classe d'application, mais nous ne pouvons pas,Foo2
la super classe de est fixe.Foo2
hérite de quelques méthodes d'instance amusantes commename(), cardinal(), compareTo(Foo2)
, qui sont juste déroutantes pourFoo2
les utilisateurs de.Foo2
ne peut pas avoir sa proprename()
méthode même si cette méthode est souhaitable dansFoo2
l'interface de.Foo2
contient également des méthodes statiques drôlesqui semble être absurde pour les utilisateurs. Un singleton ne devrait généralement pas avoir de méthodes statiques pulbiques (autres que les
getInstance()
)Sérialisabilité
Il est très courant que les singletons soient avec état. Ces singletons ne doivent généralement pas être sérialisables. Je ne peux penser à aucun exemple réaliste où il est logique de transporter un singleton avec état d'une VM à une autre VM; un singleton signifie «unique au sein d'une machine virtuelle», et non «unique dans l'univers».
Si la sérialisation a vraiment un sens pour un singleton avec état, le singleton doit spécifier explicitement et précisément ce que signifie désérialiser un singleton dans une autre VM où un singleton du même type peut déjà exister.
Foo2
s'engage automatiquement dans une stratégie simpliste de sérialisation / désérialisation. Ce n'est qu'un accident qui attend de se produire. Si nous avons un arbre de données référençant conceptuellement une variable d'état deFoo2
dans VM1 à t1, par la sérialisation / désérialisation, la valeur devient une valeur différente - la valeur de la même variable deFoo2
dans VM2 à t2, créant un bug difficile à détecter. Ce bug n'arrivera pas à l'insérialisable enFoo1
silence.Restrictions de codage
Il y a des choses qui peuvent être faites dans les classes normales, mais interdites dans les
enum
classes. Par exemple, accéder à un champ statique dans le constructeur. Le programmeur doit être plus prudent car il travaille dans une classe spéciale.Conclusion
En superposant sur enum, nous économisons 2 lignes de code; mais le prix est trop élevé, nous devons transporter tous les bagages et restrictions des énumérations, nous héritons par inadvertance des "caractéristiques" de l'énumération qui ont des conséquences inattendues. Le seul avantage allégué - la sérialisation automatique - se révèle être un inconvénient.
la source
Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);
par exemple un très bel exemple serait :;Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);
^), testé sous Java 7 et Java 8…Une instance d'énumération dépend du chargeur de classe. c'est-à-dire que si vous avez un chargeur de deuxième classe qui n'a pas le chargeur de première classe en tant que parent chargeant la même classe d'énumération, vous pouvez obtenir plusieurs instances en mémoire.
Exemple de code
Créez l'énumération suivante et placez son fichier .class dans un bocal par lui-même. (bien sûr, le pot aura la bonne structure de package / dossier)
Exécutez maintenant ce test, en vous assurant qu'il n'y a pas de copie de l'énumération ci-dessus sur le chemin de classe:
Et nous avons maintenant deux objets représentant la "même" valeur d'énumération.
Je suis d'accord qu'il s'agit d'un cas d'angle rare et artificiel, et presque toujours une énumération peut être utilisée pour un singleton java. Je le fais moi-même. Mais la question posée sur les inconvénients potentiels et cette note de prudence mérite d'être connue.
la source
Le modèle enum ne peut être utilisé pour aucune classe qui lèverait une exception dans le constructeur. Si cela est nécessaire, utilisez une usine:
la source