605 Stimmen

Was sind enums und warum sind sie nützlich?

Heute habe ich einige Fragen auf dieser Website durchgesehen und eine Erwähnung eines enum im Singleton-Muster gefunden, das angeblich thread-sichere Vorteile für eine solche Lösung bietet.

Ich habe noch nie enums verwendet und programmiere seit mehr als ein paar Jahren in Java. Und anscheinend haben sie sich viel verändert. Jetzt unterstützen sie sogar vollständige OOP innerhalb sich selbst.

Warum und wofür sollte ich enums im täglichen Programmieren verwenden?

10 Stimmen

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

774voto

sleske Punkte 77202

Sie sollten immer Enums verwenden, wenn eine Variable (insbesondere ein Methodenparameter) nur einen von einer kleinen Gruppe möglicher Werte annehmen kann. Beispiele wären Dinge wie Typkonstanten (Vertragsstatus: "permanent", "temporär", "Auszubildender"), oder Flags ("jetzt ausführen", "Ausführung verschieben").

Wenn Sie Enums anstelle von Ganzzahlen (oder String-Codes) verwenden, erhöhen Sie die Überprüfung zur Compile-Zeit und vermeiden Fehler durch die Übergabe ungültiger Konstanten. Zudem dokumentieren Sie, welche Werte verwendet werden dürfen.

Übrigens, der übermäßige Einsatz von Enums könnte bedeuten, dass Ihre Methoden zu viel tun (es ist oft besser, mehrere separate Methoden zu haben, anstatt eine Methode zu haben, die mehrere Flags akzeptiert, die ihr Verhalten modifizieren), aber wenn Sie Flags oder Typcodes verwenden müssen, sind Enums der richtige Weg.

Als Beispiel, was ist besser?

/** Zählt die Anzahl der Foobangs.
 * @param type Typ der zu zählenden Foobangs. Kann 1=grüne Foobangs,
 * 2=runzlige Foobangs, 3=süße Foobangs, 0=alle Typen sein.
 * @return Anzahl der Foobangs des Typs
 */
public int countFoobangs(int type)

verglichen mit

/** Typen von Foobangs. */
public enum FB_TYPE {
 GRÜN, RUNZLIG, SÜß, 
 /** Spezieller Typ für alle kombinierten Typen */
 ALLE;
}

/** Zählt die Anzahl der Foobangs.
 * @param type Typ der zu zählenden Foobangs
 * @return Anzahl der Foobangs des Typs
 */
public int countFoobangs(FB_TYPE type)

Ein Methodenaufruf wie:

int süßeFoobangAnzahl = countFoobangs(3);

wird dann zu:

int süßeFoobangAnzahl = countFoobangs(FB_TYPE.SÜß);

In dem zweiten Beispiel ist sofort klar, welche Typen erlaubt sind, die Dokumentation und die Implementierung können nicht aus dem Takt geraten und der Compiler kann dies durchsetzen. Außerdem ist ein ungültiger Aufruf wie

int süßeFoobangAnzahl = countFoobangs(99);

nicht mehr möglich.

154voto

Gene Punkte 44687

Warum sollte man eine Funktion einer Programmiersprache verwenden? Der Grund, warum wir überhaupt Sprachen haben, ist folgender:

  1. Programmierer sollen Algorithmen effizient und korrekt in einer Form ausdrücken können, die von Computern verstanden wird.
  2. 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.

49voto

Costi Ciudatu Punkte 35188

Neben den bereits erwähnten Anwendungsfällen finde ich Enums oft nützlich zur Implementierung des Strategiemusters, unter Einhaltung einiger grundlegender OOP-Richtlinien:

  1. Den Code dort haben, wo die Daten sind (das heißt innerhalb des Enums selbst - oder oft innerhalb der Enum-Konstanten, die Methoden überschreiben können).
  2. Die Implementierung eines Interfaces (oder mehrerer), um den Client-Code nicht an das Enum zu binden (das sollte nur eine Reihe von Standardimplementierungen bereitstellen).

Das einfachste Beispiel wäre eine Reihe von Comparator-Implementierungen:

enum StringComparator implements Comparator {
    NATURAL {
        @Override
        public int compare(String s1, String s2) {
            return s1.compareTo(s2);
        }
    },
    REVERSE {
        @Override
        public int compare(String s1, String s2) {
            return NATURAL.compare(s2, s1);
        }
    },
    LENGTH {
        @Override
        public int compare(String s1, String s2) {
            return new Integer(s1.length()).compareTo(s2.length());
        }
    };
}

Dieses "Muster" kann in weit komplexeren Szenarien verwendet werden, wobei umfangreich von allen Annehmlichkeiten Gebrauch gemacht wird, die mit dem Enum mitgeliefert werden: Iteration über die Instanzen, Verlassen auf ihre implizite Reihenfolge, Abrufen einer Instanz nach ihrem Namen, statische Methoden, die die richtige Instanz für spezifische Kontexte bereitstellen usw. Und dennoch wird dies alles hinter dem Interface verborgen, damit Ihr Code mit benutzerdefinierten Implementierungen funktioniert, ohne Änderungen vornehmen zu müssen, falls Sie etwas möchten, das nicht unter den "Standardoptionen" verfügbar ist.

Ich habe gesehen, dass dies erfolgreich für die Modellierung des Konzepts der Zeitrasterung (täglich, wöchentlich usw.) angewendet wurde, bei dem die gesamte Logik in einem Enum verkapselt war (wahl des richtigen Rasters für einen gegebenen Zeitbereich, spezifisches Verhalten, das jeder Rasterung als konstante Methoden zugewiesen wurde usw.). Und dennoch war die Granularity aus Sicht der Servicesschicht einfach nur ein Interface.

34voto

orangepips Punkte 9721

Etwas, das keiner der anderen Antworten abgedeckt hat, was Enums besonders leistungsfähig macht, ist die Möglichkeit, Vorlagenmethoden zu haben. Methoden können Teil des Basenums sein und von jedem Typ überschrieben werden. Und mit dem Verhalten, das dem Enum zugeordnet ist, wird oft die Notwendigkeit von if-else-Konstrukten oder switch-Anweisungen eliminiert, wie in diesem Blog-Beitrag dargestellt wird - wo enum.method() das ausführt, was ursprünglich innerhalb der Bedingung ausgeführt werden würde. Das gleiche Beispiel zeigt auch die Verwendung von statischen Imports mit Enums, was ebenfalls viel saubereren DSL-ähnlichen Code produziert.

Einige andere interessante Eigenschaften sind die Tatsache, dass Enums Implementierungen für equals(), toString() und hashCode() bereitstellen und Serializable und Comparable implementieren.

Für eine vollständige Übersicht über alles, was Enums zu bieten haben, empfehle ich Bruce Eckels Thinking in Java 4. Auflage, das ein ganzes Kapitel dem Thema widmet. Besonders aufschlussreich sind die Beispiele, die ein Rock, Paper, Scissors (d.h. RoShamBo) Spiel als Enums einbeziehen.

27voto

CoolBeans Punkte 20350

Von Java Dokumenten -

Sie sollten Enum-Typen verwenden, wann immer Sie einen festen Satz von Konstanten darstellen müssen. Dazu gehören natürliche Enum-Typen wie die Planeten in unserem Sonnensystem und Datensätze, bei denen Sie zur Kompilierzeit alle möglichen Werte kennen - zum Beispiel die Auswahlmöglichkeiten in einem Menü, Befehlszeilenoptionen usw.

Ein häufiges Beispiel besteht darin, eine Klasse durch einen Satz von privaten static final int-Konstanten (innerhalb einer vernünftigen Anzahl von Konstanten) durch einen Enum-Typ zu ersetzen. Grundsätzlich, wenn Sie glauben, alle möglichen Werte von "etwas" zur Kompilierzeit zu kennen, können Sie das als Enum-Typ darstellen. Enums bieten Lesbarkeit und Flexibilität gegenüber einer Klasse mit Konstanten.

Ein paar andere Vorteile, die mir bei Enum-Typen einfallen. Es gibt immer eine Instanz einer bestimmten Enum-Klasse (daher kommt das Konzept, Enums als Singleton zu verwenden). Ein weiterer Vorteil ist, dass Sie Enums als Typ in einer switch-case-Anweisung verwenden können. Außerdem können Sie toString() auf dem Enum verwenden, um sie als lesbare Zeichenfolgen auszugeben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X