Warum sollte man eine Funktion einer Programmiersprache verwenden? Der Grund, warum wir überhaupt Sprachen haben, ist folgender:
- Programmierer sollen Algorithmen effizient und korrekt in einer Form ausdrücken können, die von Computern verstanden wird.
- Wartungspersonal soll Algorithmen, die andere geschrieben haben, verstehen und korrekt ändern können.
Enums verbessern sowohl die Wahrscheinlichkeit der Korrektheit als auch die Lesbarkeit, ohne viel Boilerplate schreiben zu müssen. Wenn Sie bereit sind, Boilerplate zu schreiben, können Sie Enums "simulieren":
public class Color {
private Color() {} // Andere sollen keine Farben erstellen können.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Jetzt können Sie Folgendes schreiben:
Color trafficLightColor = Color.RED;
Die oben stehende Boilerplate hat fast den gleichen Effekt wie
public enum Color { RED, AMBER, GREEN };
Beide bieten die gleiche Prüfungshilfe des Compilers. Boilerplate bedeutet einfach mehr Tipparbeit. Aber viel Tipparbeit spart dem Programmierer Zeit (siehe 1), deshalb ist es eine lohnenswerte Funktion.
Es ist noch aus einem anderen Grund lohnenswert:
Switch-Anweisungen
Was die oben stehende Simulation von static final
enums Ihnen nicht bietet, sind schöne switch
-Fälle. Für Enum-Typen verwendet Java den Typ seiner Variablen, um den Umfang der Enum-Fälle abzuleiten, daher müssen Sie für den obigen enum Color
nur Folgendes eingeben:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Beachten Sie, dass es in den Fällen nicht Color.RED
ist. Wenn Sie kein Enum verwenden, ist der einzige Weg benannte Mengen mit switch
zu verwenden, so etwas wie:
public Klasse Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
Aber jetzt muss eine Variable, die eine Farbe halten soll, den Typ int
haben. Die schöne Compilerprüfung des Enums und der static final
Simulation ist weg. Nicht gut.
Ein Kompromiss ist die Verwendung eines skalaren Werts im Mitglied der 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);
}
Jetzt:
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
Aber beachten Sie, noch mehr Boilerplate!
Verwendung eines Enums als Singleton
Aus der oben stehenden Boilerplate sehen Sie, warum ein Enum eine Möglichkeit bietet, ein Singleton zu implementieren. Statt zu schreiben:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// alle Methoden und Instanzdaten für die Klasse hier
}
und dann darauf zuzugreifen mit
SingletonClass.INSTANCE
Können wir einfach sagen
public enum SingletonClass {
INSTANCE;
// alle Methoden und Instanzdaten für die Klasse hier
}
was uns dasselbe gibt. Wir können das machen, weil Java Enums als vollständige Klassen implementiert sind, über die nur etwas syntaktischer Zucker gestreut ist. Auch dies ist weniger Boilerplate, aber es ist nicht offensichtlich, es sei denn, das Idiom ist Ihnen vertraut. Ich mag auch nicht, dass Sie die verschiedenen Enum-Funktionen bekommen, obwohl sie für das Singleton nicht viel Sinn machen: ord
und values
usw. (Tatsächlich gibt es eine kniffligere Simulation, bei der Color extends Integer
, die mit switch funktioniert, aber sie ist so knifflig, dass sie noch deutlicher zeigt, warum enum
eine bessere Idee ist.)
Thread-Sicherheit
Thread-Sicherheit ist nur ein potenzielles Problem, wenn Singletons faul ohne Synchronisierung erstellt werden.
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// alle Methoden und Instanzdaten für die Klasse hier
}
Wenn viele Threads getInstance
gleichzeitig aufrufen, während INSTANCE
immer noch null ist, können beliebig viele Instanzen erstellt werden. Das ist schlecht. Die einzige Lösung ist, den Zugriff mit synchronized
zu schützen.
Der static final
-Code oben hat dieses Problem jedoch nicht. Er erstellt die Instanz gierig zur Load-Zeit der Klasse. Das Laden von Klassen ist synchronisiert.
Das Enum-Singleton wird effektiv faul initialisiert, weil es erst bei der ersten Verwendung initialisiert wird. Die Java-Initialisierung ist ebenfalls synchronisiert, sodass mehrere Threads nicht mehr als eine Instanz von INSTANCE
initialisieren können. Sie erhalten also einen faul initialisierten Singleton mit sehr wenig Code. Der einzige Nachteil ist die recht obskure Syntax. Sie müssen mit dem Idiom vertraut sein oder verstehen, wie Klassenladung und -initialisierung funktionieren, um zu wissen, was passiert.
10 Stimmen
Im seinem Buch Effective Java, Second Edition erläutert Joshua Bloch diesen Ansatz in Item 3: Enforce the Singleton Property with a Private Constructor or an enum Type, nachgedruckt in Dr. Dobb's.
1 Stimmen
Sie können Enums nicht instanziieren, da diese einen privaten Konstruktor haben. Sie werden beim Starten der JVM instanziiert, also handelt es sich um Singletons. Enums sind nicht thread-sicher.
0 Stimmen
Ich stimme dafür, diese Frage zu schließen, weil dieses Meta das besagt: meta.stackoverflow.com/questions/429367/closing-consistency