Wofür verwendet ihr enums?

wenn das kein Gegenvorschlag war, hast du dann einen anderen?
es ist hier nunmal ein konkretes Beispiel, auch wenn Verwendung fehlt, aber anschaulich für alles

welche Variante käme alternativ in Frage?


wiederum die Frage, wie es alternativ ohne Enum aussieht?
wenn <if, else if, else> statt switch, dann eben genauso problematisch, nur noch unschöner anzusehen,

der Satz suggeriert halb, dass speziell dieser Fall NUR bei Enum auftritt, ‚genauso‘ ein Problem wie das andere mit fehlenden Erkennen im Quellcode, was doch reichlich fraglich ist

sichlich hat die beste Lösung™ :wink: Enum eine Grundmenge X an Problemen, alle Alternativen diese aber auch + zusätzliche Probleme

es hilft ja auch wenig, etwa ‚ein Enum kann (auch) zu einer NullPointerException führen‘ zu erwähnen, wenn bei jeder Alternative genauso…

Das stimmt auch wiederum. Code, der ggf. mit Enums funktioniert, aber nicht umgekehrt, wäre damit ggf. „gültig“, und Probleme könnten damit vielleicht erst/nur bei der Erweiterbarkeit auftreten. Jedenfalls verstehe ich dich. :slight_smile:

Dennoch bieten Enums vielleicht mehr Sicherheiten, aber sie sind vielleicht auch mit Einschränkungen verbunden. :wink: (Welche hier jetzt gerade diskutiert werden)

Sicher, definierte Zustände in einem definierten Zustandsraum hatte ich ja als Beispiel genannt. Und wie wichtig die Frage ist, ob das public ist und clients das sehen, sieht man ja schon an den verschiedenen Formen der Kompatibilität.

Unter der Haube sind enums in Java ziemlich genau das. Mit ein bißchen Schmuck für switch & Co.

Nun, das Beispiel ist zwar anschaulich, macht aber nur bedingt Sinn, solange es keinen „Kontext“ gibt, in dem man über das Design nachdenkt. „Planeten sind eine Aufzählung“ würde man ja so nicht sagen - das Beispiel dient dort ja nur dazu, zu illustrieren, dass enums auch „komplex“ sein können (und nicht nur verkappte ints, wie in C++). Der Ansatz, bei dem man nicht viel falsch machen kann, ist eben

interface Planet { ... }
interface SolarSystem { Planet get(String planetName); }
SolarSystem ourSolarSystem = SolarSystems.createOurs(); 

Ein „verstecktes Anti-Pattern“ bei enums kann noch sein, dass man mit switch modelliert, was eigentlich über Polymorphie abgebildet werden sollte (aber die Frage sollte man sich IMHO bei jedem switch (und sogar bei den meisten ifs) stellen…)


Aber nochmal: Es geht nicht direkt um Vor- oder Nachteile der einen oder anderen Lösung, sondern wirklich ganz abstrakt um den Punkt, dass ich finde, dass enums nur und genau dann verwendet werden sollten, wenn man eine „Domänenspezifische Auflistung von Elementen“ hat (die sich selten bis nie ändert), und eben nicht als „Strings ohne Anführungszeichen“.

vorstellbar, falls nicht zu abstoßend, ist nebenher noch die Variante einer Enum nur für die platte Unterscheidung, Basisdaten (Name), Nummerierung,
und trotzdem noch komplexe Klassen dazu, evtl. sehr umfangreich erst geladen/ aufgebaut usw.

enum PlanetKey …
Planet get(PlanetKey key)

Meistens nur für Types, also Dinge die man anno dazumal mit ints definiert hat. Seit kurzer Zeit erwische ich mich auch dabei, Factorys, die Konstanten bieten (z.B. Eineiten-Umrechner als Enums zu implementieren, welche dann natürlich ein Interface implementieren. Dann kann man zumindest switch auf die Konstanten anwenden, bei produzierten Objekten durch die Factory-Methode funktioniert das leider nicht.

Ich glaub, dass ich das Thema erstmal falsch verstanden habe @Marco13 wollte wissen, für was für Usecases wir enums einsetzen, und gar nicht nur unbedingt das Planetenbeispiel diskutieren. Spontan fallen mir 2 Sachen ein, wofür ich enums verwendet habe.

  1. Angenommen ich habe einen RESTEndpoint der eine Liste von Personen zurück gibt. Als Parameter kann ein Sortierkriterium angegeben werden. Die Menge der Sortierkriterien ist beschränkt, könnte sich aber mit der Zeit erweitern. Ich hab das so implementiert:
public enum PersonComparators implements Comparator<Person> {
  FIRST_NAME(Comparator.comparing(Person::getFirstName)),  
  LAST_NAME(Comparator.comparing(Person::getLastName));

  private final Comparator<Person> comp;

  private PersonComparators(Comparator<Person> comp) {
    this.comp = comp;
  }
  
  @Override
  public int compare(Person o1, Person o2) {
    return comp.compare(o1, o2);
  }  
}

und der Service dazu sieht ungefähr so aus:

public class PersonService {
  
  public Stream<Person> getPersons(String orderBy) {
    PersonComparators order = PersonComparators.valueOf(orderBy);
    Stream<Person> persons; //=...
    return persons.sorted(order);
  }  
}
  1. Beispiel war ein Parser. Das kongrete Beispiel wäre zu komplex für hier, aber angenommen, ich habe eine Lampe und einen Parser der Ausdrücke der Form “ON#OFF#ON#ON#OFF#ON#OFF” entgegen nimmt und dann auswerten kann, was der finale Zustand der Lampe ist.

Das könnte man so implementieren:

public enum LightState {
  ON {
    @Override
    public LightState next(String state) {
      if ("ON".equals(state)) {
        return ON;
      } else if ("OFF".equals(state)) {
        return OFF;
      } else {
        return UNDEFINED;
      }
    }
  },
  OFF {
    @Override
    public LightState next(String state) {
      if("ON".equals(state)) {
        return OFF;
      } else if("OFF".equals(state)) {
        return OFF;
      } else {
        return this;
      }
    }
  },
  UNDEFINED {
    @Override
    public LightState next(String state) {
      return UNDEFINED;
    }
  };

  public abstract LightState next(String state);
}

Und der Parser sieht so aus:

public class LightParser {
  
  public LightState getFinalState(LightState init, String expression) {
    String[] parts = expression.split("#");    
    LightState result = init;
    
    for (String part : parts) {
      result = result.next(part);
    }
    
    return result;    
  }  
}

Beide Fälle sind beliebig erweiterbar (OK bei der Lampe schwer vorstellbar, aber mein Usecase für den Parser war komplexer)

An dem ganzen Beispiel+Code läßt jetzt nur eine (vermeintliche) Kleinigkeit bei mir ein Fragezeichen erscheinen, das für diesen Thread speziell relevant ist: Das public. Warum sollte diese enum denn public sein? Die Schnittstelle nach draußen ist der orderBy-String.

Ob das intern irgendwo in eine enum verwandelt wird, ist so gesehen nicht so wichtig. Aber gerade weil es in diesem speziellen Beispiel besonders unwichtig ist, reite ich nochmal länger drauf rum: Die Schnittstelle, im Sinne der „abstrahiertesten Funktionalität, die (wenn auch nur intern!) angeboten werden muss“, ist:

  • String rein. Comparator<Person> raus.

Man könnte das gleiche auch so erreichen:

class PersonComparators {
    static Comparator<Person> valueOf(String orderBy) {
        switch (orderBy) {
            case "FirstName": return Comparator.comparing(Person::getFirstName);
            ...
        }
    }
}

Welchen konkreten Zweck hat an dieser Stelle die enum?

(Dass man das switch oben noch mit einer Function<Person, Comparable<?>> verallgemeinern oder ganz gewagt mit Reflection aufbohren könnte, um auch 100 Sortierkriterien mit 5 Zeilen Code abzufrühstücken, könnte ich jetzt erwähnen, aber darauf könnte man lange rumhacken. Der wichtige Punkt ist: Warum läßt man die enum nicht einfach weg?)

Wenn es nicht um Sortierkriterien ginge, die (je nach darunterliegender Klasse und gewünschter Funktionalität beliebig sein können (und sich ggf. auch leicht ändern können)), wäre das etwas anderes. Sowas hier zum Beispiel:

enum SortOrder {
    ASCENDING,
    DESCENDING,
    UNSORTED
}

Das ist fest. Das macht Sinn. Das ändert sich nie. (Nur ein Beispiel…)


Das ist genau der „Zustandsautomat“, den ich schon als legitimen use-case erwähnt hatte. (Dass die Zustandsübergangslogik IMHO nicht unbedingt in die Konstanten gehört, ist ein anderer Punkt).

Das passt doch hierzu:

https://www.spiegel.de/wissenschaft/weltall/ngts-4b-diesen-exoplaneten-sollte-es-nicht-geben-a-1270094.html

Eine Programmarchitektur besteht immer aus abstrakten Funktionalitäten und projektspezifischen Code der daraus erst „das Projekt“ macht.
Ein Programm das Pluto als enum führt, hat Pluto auch architektonisch und funktionell verankert. Kein abstraktes „Rendere ein Sternensystem“-Programm hat Pluto fest einprogrammiert, sondern wird irgendwo die von Marco13 angesprochenen Listen führen die dynamisch verändert werden können. Ein „Lerne unser Sternsystem“-Programm könnte schon eher Pluto fest verankert haben, dann ist aber der Wegfall des enums nur das Randproblem von einem viel größeren architektonischen und funktionellen Problem.

Also zu sagen

ist meiner Ansicht auch falsch. Ein enum wird dann geführt wenn es der Projektfunktionalität dienlich ist: ALLES kann ein enum sein.

Im Umkehrschluss sind enums immer dann falsch eingesetzt, wenn nur der Wegfall einer Enumeration selbst ein Problem auslösen würde.

Irgendwie habe ich das Gefühl, dass mein Punkt nicht ganz ankommt (oder ich habe eine zu andere Sicht auf bestimmte Dinge). Aber ich hoffe, dass niemand mal den Code eines Kollegen reviewt, der eine Kundendatenbank schreiben sollte, und dann (und das soll jetzt nur ein etwas (hoffentlich!?!) übertriebenes Beispiel sein, um den Punkt zu verdeutlichen) sowas sieht wie

enum Customer {
   JOHN_DOE("Main Street 4", "Footown"),
   JANE_SMITH("Second Street 5", "Bar City"),
   STEVE_MILLER("Broadway 12", "Springfield");
}

Den Unterschied zu dem Planet-Fall kann jetzt gerne jemand versuchen, herauszuarbeiten.

Ich versteh immer noch nicht ganz deinen Punkt. Ich glaube der Unterschied zwischen deiner und meiner Definition ist doch, dass du sagst, die Anzahl der Literale muss für eine seeehhhr lange Zeit genau gleich bleiben.
Meine ist, dass wenn ich weiß, dass es sich zum Zeitpunkt der Entwicklung um eine beschränkte Menge handelt und die Wahrscheinlichkeit, dass es jemals zur Laufzeit dynamisch sein wird, klein ist, dann nutze ich enums.
Für mich könnte man deine Argumentation genauso gegen Properties anwenden. In irgendeinem Tutorial steht als Beispiel für eine Klasse:

class Person {
   ....
   String getName() {return name;}
   String getEmail() {return email;}
}

Und in einer frühren Version stand mal:

class Person {
   String getName() {return name;}
   String getPagerNumber() { return pagerNumber;}
}

Da wäre es doch besser gewesen das so zu machen:

class Person {
   String getProperty(String property) { .... }
}

Vielleicht übersehe ich ja was wichtiges, aber bis jetzt sagst du nicht warum du es so machst.

also während ich das Thema an sich als normales bekanntes Level an Spitzfindigkeit/ Hinterfragen von dir sehe :wink: , dann mit diesem Vergleich aber doch nun endgültig übers Ziel hinausgeschossen…

im Vergleich zu Customer-enum in einer Kundendatenbank wäre Planet-enum genauso sinnlos in einer Planetendatenbank(!) zum Aufbau beliebiger einzugebener Sonnensystem oder mehr, ohne jeglichen konkreten Bezug auf irgendwas, Erde & Co. müssen nicht existieren,

ein konkretes Programm mit Enum Planet ist doch wohl offensichtlich etwas völlig anderes,
auf das aktuelle Sonnensystem bezogen mit wer weiß wie vielen Referenzen und Regeln und Anwendungen alle nur in Bedenken und Bezug auf diese (relativ…) feste Menge an vorgegebenen Planeten,

konkrete Beispiel dazu auszudenken, wo Programm nicht vorhanden, ist schwierig,
aber vielleicht sowas wie Verknüpfung imaginäre Wegpunkte/ günstige Verbindungen, über den jeweils nähesten Nachbarn hinaus,

ja, auch eine Enum der Monde ist vorstellbar, je nach Umsetzstil,
und einfach leicher owner = Planet.Xy; zu setzen statt was auch immer an Alternative, komplizierte Objekte aus Factories, obwohl Monde nur eine leichtgewichtige Info-Tabelle darstellt, deren komplette Initalisierung bei ersten Zugriff nicht gleich das gesamte Sonnensystem aktiv in den Speicher laden soll…

diese beiden Beispiele sind Initialisierungen, Informationen voreingetragen, im Quellcode modelliert statt bei jedem Programmstart mühevoll einzugeben oder in einer DB gespeichert,
so ist das eben wenn es um ein konkretes Programm mit konkreten Inhalt geht,

was es sonst noch alles gibt…

boolean isLebenswert() {
 switch  case Erde: 
 default: 
}

würdest du ja sicher auch als falsche Umsetzung von Polymorphie sehen, aber einerseits möglicher Stil eines Programms,
oder auch vielleicht gibt es Programmmodule mit Zusatz-Informationen/ Unterscheidungen, die im Quellcode des Haupt-package unbekannt sind und und und,

ein Programm mit zentralen 10 Planeten kann so unendlich viele Anwendungen für eine Enum dieser 10 Planeten haben,
oder wenn nicht, nun dann ja auch kein Problem, dann Planet.PLUTO eben doch nie im Quellcode einzeln genannt…

eine Planetendatenbank wiederum hätte wegen der Möglichkeit neuer Daten andere Strukturen nötig,
vielleicht aber ein Skript zum Aufbau für das bekannte Sonnensystem-Beispiel vorhanden, mit Strings oder vorstellbar doch auch nur für diesen Initialisierungscode vereinfacht wiederum Enum vorhanden,
aber dann eben nicht im allgemeinen Hauptprogramm benötigt, sondern wiederum in einem konkreten Unterprogramm mit wieder den genauen Bezug auf die konkreten Planeten

wann sollte je eine Kundendatenbank einen einzelnen Kunden konkret ansprechen, für welchen Zweck?
wer initialisiert je eine Kundendatenbank mit bestimmten Namen?
wobei nicht ganz außer Acht zu lassen, eigene Mitarbeiter, Premium-Kunden über mehrere Datenbanken gleich usw. :wink:

aber wahrscheinlich dadurch gerettet dass es dann trotzdem besser nur ein Format gibt, diese Kunden also am Anfang per Skript in DB einzufügen oder was auch immer…


edit:
wenn es ähnlich Sonnensystem in allgemeiner Planetendatenbank hier auch ein Skript gegen soll um 20 Kunden mit diversen Beziehungen testweise zu erstellen, kann man dafür durchaus auch ähnlich eine Enum verwenden :wink:
nicht zur Verwendung im Programm, in die dynamischen Programmstrukturen zu überführen, aber allein für die Initialisierung

1 „Gefällt mir“

enum gibt es seit Java 1.5, das wurde 2004 released, damals ging man davon aus dass “9 Planeten” etwas statisches ist dass sich mal eben nicht von jetzt auf gleich aendert, Sun dachte eben das es ein tolles Beispiel dafuer ist wofuer enums gut sind… 2006 wurde dann Pluto von jetzt auf gleich der Planetenstatus aberkannt ¯\(ツ)

Ich denke man haette ruhig ein neues, besseres Beispiel finden koennen, IMHO sind Wochentage wirklich statisch, aber Datumsoperationen werden schnell komplex, ehrlich gesagt hab ich auf die schnelle kein besseres parat, das mit den 63 Geschlechtern wurde ja schon von Tomate angesprochen.

Das letztere wäre dann ja praktisch wie JavaScript :nauseated_face: Mal im Ernst: Ich stelle hier ja nicht den Sinn von Objektorientierter Programmierung in Frage.


<halbOffTopic>

Den Sinn von Klassen würde ich in Frage stellen, in Anlehnung an das Zitat

Q: „If you could do Java over again, what would you change?“
James Gosling: „I’d leave out classes“

Das wichtige sind Interfaces. Und auch bei Interfaces ist es so: Sie sollten sich nie ändern. Wenn auch nur der geringste Zweifel besteht, dass irgendwas in einem Interface für immer ist, sollte man es Weglassen. Noch ein Zitat dazu:

„When in doubt, leave it out“ (Joshua Bloch)

(Natürlich ändern sich interfaces. Aber sobald irgendwas public ist, und man es wegnehmen will, hat man ein Problem).

</halbOffTopic>


Zurück zum Kern des Themas, der aber nicht zu sehr auf das Beispiel bezogen sein sollte:

:sunglasses:

Das war zu einem gewissen Grad Absicht - eben um Leute dazu zu provozieren, sich über die Frage Gedanken zu machen, wo denn eigentlich der Unterschied ist. Das hast du offenbar getan: Die nachfolgende „Wall Of Text“ pflückt genau dieses Besipiel auseinander. Das ganze ist etwas zu speziell, und etwas zu ausführlich, als dass es Sinn machen würde, auf jeden einzelnen Aspekt einzugehen.

Aber noch kurz relativierend dazu, dass (oder warum) dieses Beispiel „schlecht“ ist:

Tatsächlich war diese Aufzählung >70 Jahre lang fest und richtig, und das ist durchaus eine akzeptable Halbwertszeit für Software.

(„Oh nein, angular-cli-1.3.4.1.3 vom Januar 2019 und angular-bootstrap-1.3.4.1.4 vom Februar 2019 sind inkompatibel, wir müssen erstmal alles updaten!!!111“).

Aber die Frage zielte tatsächlich auf etwas allgemeineres, theoretischeres ab.


Ich hatte versucht, im Eröffnungsbeitrag den Punkt rauszuarbeiten: Ich finde, mit einer enum (Enumeration, Aufzählung) sollte bewußt etwas modelliert werden. Die Elemente dieser enum sollten nicht „irgendwelche Instanzen einer Klasse sein (die es (gerade, im Moment) gibt)“.

Eine etwas formalere und stärkere Formulierung (wenn auch nicht „perfekt“ im formalen Sinn) wäre:

Bei einer enum ist die Menge der Instanzen ein unveränderlicher Teil des Datentyps an sich!

Bei einem Datentyp wie Person beschreibt die Klasse die „Schablone“, ganz klassisch im OO-Sinn. Man kann beliebig viele Instanzen davon erstellen. Diese Instanzen haben alle den gleichen Datentyp, nämlich Person. Welche Instanzen es gibt, spielt für den Datentyp aber keine Rolle. Die wichtige Frage, wann zwei Instanzen „die gleiche“ sind, wird mit equals behandelt. Genauso ist es bei Planet.

Der Datentyp Person beschreibt also ~„Die Menge aller Elemente, die einen Vor- und Nachnamen haben“. Man kennt sie nicht im einzelnen. Und man kann sie nicht aufzählen (!).

Anders ist es bei sowas wie DayOfWeek (InGregoranCalendar, wenn’s sein muss). Man könnte sowas machen wie

enum DayOfWeek { MO, DI, MI, DO, FR, SA, SO }

und damit ist der Datentyp an sich beschrieben: Der Datentyp an sich hat genau diese Elemente. Wenn es SA und SO nicht mehr gibt, dann ist das nicht mehr der gleiche Datentyp (sondern vielleicht ein anderer, wie DayOfWorkWeek). Wenn dort ein Element wie XY dazukommt, dann ist das auch nicht mehr der gleiche Datentyp.

Der Datentyp DayOfWeek beschreibt also die Menge { MO, DI, MI, DO, FR, SA, SO }. Und genau die. Ein DI ist immer dasselbe wie ein „anderes“ DI. Es gibt einfach nur diese 7 Elemente. Die, die man dort aufgezählt hat.


Vielleicht sind die Fragen und Gedanken, die ich mir dazu mache, Spätfolgen davon, dass ich mal ein paar Vorlesungen bei einem Prof gehört habe, der sich viel mit https://user.phil.hhu.de/~petersen/slides/begriffe1.pdf beschäftigt hat, aber das führt jetzt vielleicht zu weit.

Natürlich sind die Beispiele die du listest suboptimal und auch das was da dein Kollege macht. Ich denke niemand widerspricht dir da an diesem Punkt. Aber du hast halt in deinem Eingangspost enums Generalverteufelt mit einer Regelung die nicht sinnvoll ist. Natürlich stürzen sich alle darauf.

Gerade wieder dein nächstes Beispiel: Nicht überall auf der Welt besteht die Woche aus 7 Tagen.
Da haben sich die Leute in der Antike damals an den bekannten Himmelskörpern und den Mondphasen ausgerichtet und dann hin und her gerundet, das auch hier kein Zusammenhang mehr besteht. Jetzt kommt morgen einer und sagt: Lasst uns die Wochentage doch mal ans Jahr ausrichten und 5 Tage Wochen machen. Oder ein 366 Tage Jahr, mit 6 Tage Wochen. Dann können wir auch gleich die Anzahl der Monate halbieren, dann sind die zumindest alle gleich lang von der Anzahl der Tage her, kann sich doch kein Schwein merken.
Und jetzt? Enums sind praktisch nie 100% sicher. Am besten direkt löschen, und nie wieder verwenden, hat doch vorher auch ohne geklappt.

Für mich sind enums typische Paradigmen die Anfällig für Programmierer “Ticks” sind, ähnlich wie bei Premature Optimizations, gibt es immer wieder Phasen zwischen exzessiver Übertreibung und völliger Verdrängung.

Ein Anwendungsbereich wurde noch gar nicht angesprochen: Algebraische Datentypen.

Beispiel:

interface List<T> {
     boolean isEmpty();
     int size(); 
     ...
}

enum Nil implements List<Object> {
    NIL;
    boolean isEmpty() { return true; }  
    int size() { return 0; }  
    ...
}

class Cons<T> implements List<T> {
    ...
}

Ich habe enums nicht „generalverteufelt“. Die Bedingungen, unte denen enums angebracht sind, hatte ich direkt genannt:

Das war etwas „informell“. Die Formulierung, dass die Instanzmenge Teil des Datentyps ist, ist etwas formaler.

@Landei Kein Grund für enum. Ersetze enum durch class, und das Ergebnis ist das gleiche.

Irgendwie passt das hier hin:

Auch wenn das Problem dort nicht wirklich “die enums an sich” sind, ist es nur ein Beispiel dafür, dass Leute enums für den letzten Mist verwenden…

Ideally, I would like to know if there some way to define these constants in some config file and read those files to create the constants. Is there a way to create such ENUM constants by reading and parsing some config file?

Wow, okay, und die Antworten sind auch nicht besser. Das ist so ein typisches Projekt wo einer mal wahrscheinlich angefangen hat zwei/drei enums zu definieren (US/EU/JP), und dann fängt das Projekt an zu wachsen.

Ich verwende gern Emuns für eine feste Auswahl an Dingen in meinen Programmen, für die ich gern Typsicherheit hätte.

Große Liste an Konstanten halte ich aber in der Art

public static final String IMPORTANT_CONSTANT = "foo";

vor. An Enums mag ich sehr, dass man den Konstanten auch noch ihnen fest zugeordnete andere Paramter mitgeben kann, so lässt sich dann alles an einer Stelle konfigurieren und man spart sich eigentlich sinnlose Switch-Anweisungen.