Warum eine Programmiersprachenfunktion verwenden? Der Grund, warum wir überhaupt Sprachen haben, ist für
- Programmierer, die Algorithmen effizient und korrekt in einer Form ausdrücken können, die Computer verwenden können.
- Maintainer müssen Algorithmen verstehen, die andere geschrieben haben, und Änderungen korrekt vornehmen.
Aufzählungen verbessern sowohl die Wahrscheinlichkeit der Korrektheit als auch die Lesbarkeit, ohne viel Boilerplate zu schreiben. Wenn Sie bereit sind, Boilerplate zu schreiben, können Sie Aufzählungen "simulieren":
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();
}
Jetzt können Sie schreiben:
Color trafficLightColor = Color.RED;
Die Kesselplatte oben hat fast den gleichen Effekt wie
public enum Color { RED, AMBER, GREEN };
Beide bieten die gleiche Überprüfungshilfe vom Compiler. Boilerplate ist nur mehr Tippen. Wenn Sie jedoch viel tippen, wird der Programmierer effizienter (siehe 1), sodass sich diese Funktion lohnt.
Es lohnt sich auch aus mindestens einem weiteren Grund:
Anweisungen wechseln
Eine Sache, die Ihnen die static final
obige Aufzählungssimulation nicht bietet , sind schöne switch
Fälle. Bei Aufzählungstypen verwendet der Java-Switch den Typ seiner Variablen, um den Umfang der Aufzählungsfälle abzuleiten. Für die enum Color
oben genannten Fälle müssen Sie lediglich Folgendes sagen:
Color color = ... ;
switch (color) {
case RED:
...
break;
}
Beachten Sie, dass dies nicht Color.RED
der Fall ist. Wenn Sie keine Aufzählung verwenden, können Sie benannte Mengen nur mit switch
folgenden Methoden verwenden:
public Class 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 enthält, einen Typ haben int
. Die nette Compilerprüfung der Enumeration und der static final
Simulation ist weg. Nicht glücklich.
Ein Kompromiss besteht darin, ein skalarwertiges Element in der Simulation zu verwenden:
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!
Verwenden einer Aufzählung als Singleton
Auf der Boilerplate oben können Sie sehen, warum eine Aufzählung eine Möglichkeit bietet, einen Singleton zu implementieren. Anstatt zu schreiben:
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
und dann mit darauf zugreifen
SingletonClass.INSTANCE
wir können nur sagen
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
das gibt uns das gleiche. Wir können damit durchkommen , weil Java Aufzählungen sind als volle Klassen mit nur wenig syntaktischen Zucker über die oberen bestreut umgesetzt. Dies ist wieder weniger Boilerplate, aber es ist nicht offensichtlich, es sei denn, die Redewendung ist Ihnen vertraut. Ich mag auch nicht die Tatsache, dass Sie die verschiedenen Enum-Funktionen erhalten, obwohl sie für den Singleton nicht viel Sinn machen: ord
und values
usw. (Es gibt tatsächlich eine schwierigere Simulation, bei der Color extends Integer
das mit Switch funktioniert, aber es ist so schwierig, dass es noch mehr ist zeigt deutlich warumenum
eine bessere Idee ist.)
Gewindesicherheit
Die Thread-Sicherheit ist nur dann ein potenzielles Problem, wenn Singletons ohne Verriegelung träge erstellt werden.
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
}
Wenn viele Threads getInstance
gleichzeitig aufrufen, während sie INSTANCE
noch null sind, können beliebig viele Instanzen erstellt werden. Das ist schlecht. Die einzige Lösung besteht darin, synchronized
Zugriff hinzuzufügen , um die Variable zu schützen INSTANCE
.
Der static final
obige Code weist dieses Problem jedoch nicht auf. Es erstellt die Instanz eifrig zur Ladezeit der Klasse. Das Laden der Klasse wird synchronisiert.
Der enum
Singleton ist effektiv faul, da er erst bei der ersten Verwendung initialisiert wird. Die Java-Initialisierung wird ebenfalls synchronisiert, sodass mehrere Threads nicht mehr als eine Instanz von initialisieren können INSTANCE
. Sie erhalten einen träge initialisierten Singleton mit sehr wenig Code. Das einzig Negative ist die eher obskure Syntax. Sie müssen die Redewendung kennen oder genau wissen, wie das Laden und Initialisieren von Klassen funktioniert, um zu wissen, was passiert.