Pourquoi utiliser une fonction de langage de programmation? La raison pour laquelle nous avons des langues est
- Les programmeurs peuvent exprimer efficacement et correctement les algorithmes sous une forme que les ordinateurs peuvent utiliser.
- Les mainteneurs doivent comprendre les algorithmes que d'autres ont écrits et apporter correctement les modifications.
Les énumérations améliorent à la fois la probabilité de correction et la lisibilité sans écrire beaucoup de passe-partout. Si vous êtes prêt à écrire un passe-partout, vous pouvez "simuler" des énumérations:
public class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Vous pouvez maintenant écrire:
Color trafficLightColor = Color.RED;
Le passe-partout ci-dessus a à peu près le même effet que
public enum Color { RED, AMBER, GREEN };
Les deux fournissent le même niveau de vérification de l'aide du compilateur. Boilerplate est juste plus typé. Mais économiser beaucoup de frappe rend le programmeur plus efficace (voir 1), c'est donc une fonctionnalité intéressante.
Cela vaut également la peine pour au moins une autre raison:
Instructions de changement
Une chose que la static final
simulation ENUM ci-dessus ne vous donne est bien des switch
cas. Pour les types enum, le commutateur Java utilise le type de sa variable pour déduire la portée des cas enum, donc pour ce qui enum Color
précède, vous devez simplement dire:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Notez que ce n'est pas Color.RED
dans les cas. Si vous n'utilisez pas enum, la seule façon d'utiliser des quantités nommées avec switch
est quelque chose comme:
public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Mais maintenant, une variable pour contenir une couleur doit avoir un type int
. Le bon compilateur vérifiant l'énumération et la static final
simulation a disparu. Pas heureux.
Un compromis consiste à utiliser un membre à valeur scalaire dans la simulation:
public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Maintenant:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Mais attention, encore plus de passe-partout!
Utilisation d'une énumération comme singleton
Sur le passe-partout ci-dessus, vous pouvez voir pourquoi une énumération fournit un moyen d'implémenter un singleton. Au lieu d'écrire:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
puis y accéder avec
SingletonClass.INSTANCE
on peut juste dire
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
ce qui nous donne la même chose. Nous pouvons nous en tirer car les énumérations Java sont implémentées en tant que classes complètes avec seulement un peu de sucre syntaxique saupoudré sur le dessus. C'est encore moins passe-partout, mais ce n'est pas évident à moins que l'idiome ne vous soit familier. Je n'aime pas non plus le fait que vous obteniez les diverses fonctions d'énumération même si elles n'ont pas beaucoup de sens pour le singleton: ord
et values
, etc. (Il y a en fait une simulation plus délicate où Color extends Integer
cela fonctionnera avec switch, mais c'est tellement difficile montre clairement pourquoi enum
est une meilleure idée.)
Sécurité des fils
La sécurité des threads n'est un problème potentiel que lorsque les singletons sont créés paresseusement sans verrouillage.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
Si de nombreux threads appellent getInstance
simultanément alors qu'il INSTANCE
est toujours nul, un nombre quelconque d'instances peut être créé. C'est mauvais. La seule solution est d'ajouter un synchronized
accès pour protéger la variable INSTANCE
.
Cependant, le static final
code ci-dessus n'a pas ce problème. Il crée l'instance avec impatience au moment du chargement de la classe. Le chargement des classes est synchronisé.
Le enum
singleton est effectivement paresseux car il n'est initialisé qu'à la première utilisation. L'initialisation Java est également synchronisée, donc plusieurs threads ne peuvent pas initialiser plus d'une instance de INSTANCE
. Vous obtenez un singleton initialisé paresseusement avec très peu de code. Le seul point négatif est la syntaxe plutôt obscure. Vous devez connaître l'idiome ou bien comprendre comment fonctionnent le chargement et l'initialisation des classes pour savoir ce qui se passe.