Soll ich in Java ein reversibles Enum erstellen oder gibt es einen besseren Weg?

8

Ich habe dieses Problem schon oft gesehen und wollte die Gemeinde fragen, ob ich gerade den falschen Baum anbreche. Grundsätzlich kann meine Frage darauf heruntergebrochen werden: Wenn ich ein Enum (in Java) habe, für das die Werte wichtig sind, sollte ich überhaupt ein Enum verwenden oder gibt es einen besseren Weg, und wenn ich ein Enum verwende, dann was ist der beste Weg, um die Suche umzukehren?

Hier ist ein Beispiel. Angenommen, ich möchte eine Bean erstellen, die einen bestimmten Monat und ein bestimmtes Jahr repräsentiert. Ich könnte etwas wie das Folgende erstellen:

%Vor%

Hier speichere ich meinen Monat als separate Klasse namens "Monat", so dass er typsicher ist. Wenn ich nur int setze, könnte jeder 13 oder 5,643 oder -100 als Zahl eingeben, und es würde keine Möglichkeit geben, dies zur Kompilierzeit zu überprüfen. Ich beschränke sie, einen Monat zu setzen, den ich als enum implementieren werde:

%Vor%

Nun nehme ich an, dass ich eine Backend-Datenbank habe, in die ich schreiben möchte, die nur die Integer-Form akzeptiert. Nun, der übliche Weg, dies zu tun, scheint zu sein:

%Vor%

Ziemlich einfach, aber was passiert, wenn ich diese Werte aus der Datenbank lesen und schreiben möchte? Ich könnte einfach eine statische Funktion implementieren, die eine case-Anweisung innerhalb der enum verwendet, die einen int-Wert annimmt und das entsprechende Month-Objekt zurückgibt. Aber das bedeutet, wenn ich etwas ändere, dann müsste ich diese Funktion ebenso wie die Konstruktorargumente ändern - an zwei Stellen wechseln. Also hier ist was ich gemacht habe. Zunächst habe ich eine reversible Kartenklasse wie folgt erstellt:

%Vor%

Dann habe ich dies in meiner enum anstelle der Konstruktormethode implementiert:

%Vor%

Jetzt macht das alles, was ich will, aber es sieht immer noch falsch aus. Leute haben mir vorgeschlagen, "wenn die Aufzählung einen bedeutungsvollen internen Wert hat, sollten Sie stattdessen Konstanten verwenden". Ich weiß jedoch nicht, wie mir diese Vorgehensweise die Typensicherheit geben würde, nach der ich suche. Die Art, wie ich mich entwickelt habe, scheint jedoch übermäßig kompliziert zu sein. Gibt es einen Standard-Weg, um so etwas zu tun?

PS: Ich weiß, dass die Wahrscheinlichkeit, dass die Regierung einen neuen Monat hinzufügt, ... ziemlich unwahrscheinlich ist, aber denke an das Gesamtbild - es gibt viele Verwendungsmöglichkeiten für Enums.

    
Kidburla 17.10.2009, 18:58
quelle

4 Antworten

11

Dies ist ein sehr häufiges Muster, und es ist in Ordnung für Enums ... aber es kann einfacher implementiert werden. Es ist keine "reversible map" erforderlich - die Version, die die Monatszahl im Konstruktor verwendet, ist besser für den Übergang von Month nach int . Aber auch anders geht es nicht:

%Vor%

Im konkreten Fall von Zahlen, von denen Sie wissen, dass sie von 1 bis N gehen, könnten Sie einfach ein Array verwenden - entweder Month.values()[value - 1] oder den Rückgabewert von Month.values() zwischenspeichern, um zu verhindern, dass bei jedem Aufruf ein neues Array erstellt wird. (Wie Cletus sagt, könnte getMonthNum einfach ordinal() + 1 zurückgeben.)

Es ist jedoch wichtig, das obige Muster im allgemeineren Fall zu beachten, in dem die Werte nicht in Ordnung oder dünn verteilt sind.

Es ist wichtig zu beachten, dass der statische Initialisierer ausgeführt wird nachdem alle Enum-Werte erstellt wurden. Es wäre schön, einfach zu schreiben

%Vor%

im Konstruktor und fügen Sie einen statischen Variableninitialisierer für numberToMonthMap hinzu, aber das funktioniert nicht - Sie erhalten sofort NullReferenceException , weil Sie versuchen würden, den Wert in eine Karte zu schreiben, die nicht Es gibt noch keine: (

    
Jon Skeet 17.10.2009, 19:10
quelle
4

Es gibt einen einfacheren Weg, dies zu tun. Jedes Enum hat eine Methode ordinal() gib seine Nummer zurück (beginnend mit Null).

%Vor%

Wie Sie dies in einer Datenbank speichern können, hängt davon ab, welches Persistenz-Framework (falls vorhanden) Sie verwenden. JPA / Hibernate bietet die Möglichkeit, Enumerationswerte entweder durch Nummer (Ordnungszahl) oder Name zuzuordnen. Monate sind etwas, das Sie wahrscheinlich als unveränderlich nehmen können, verwenden Sie einfach die Ordinalzahl. Um einen bestimmten Wert zu erhalten:

%Vor%     
cletus 17.10.2009 19:06
quelle
3

Ich bin hier wahrscheinlich weit hinter dem Rudel zurück, aber ich tendiere dazu, es ein wenig einfacher zu implementieren. Vergiss nicht, dass "Enum" eine values ​​() -Methode hat.

%Vor%     
Beirti 27.09.2010 14:51
quelle
1

Sie sollten ordinal () nicht für diese Art von Dingen verwenden, für das Beispiel mit Monaten würde es funktionieren (weil es nicht erweitert wird), aber eines der guten Dinge mit enums in Java ist, dass sie so aussehen möglich zu verlängern, ohne Dinge zu brechen. Wenn Sie sich auf ordinal () verlassen, werden die Dinge brechen, wenn Sie einen Wert in der Mitte hinzufügen.

Ich würde es so machen, wie es Jon Skeet vorschlägt (er schrieb es, während ich dies schrieb), aber für Fälle, in denen die interne Zahldarstellung in einem wohldefinierten Bereich von etwa 0 bis 20 (oder etwas) ist, würde ich wahrscheinlich kein verwenden HashMap und autoboxing des int einführen, sondern ein gewöhnliches Array verwenden (wie Month [12]), aber beide sind in Ordnung (Jon hat später seinen Beitrag geändert, um diesen Vorschlag zu enthalten).

Bearbeiten: Für die wenigen enums, in denen es eine natürliche Reihenfolge gibt (wie sortierte Monate), ist ordinal () wahrscheinlich sicher zu verwenden. Die Probleme, in die Sie geraten könnten, wenn Sie fortfahren, werden für Dinge angezeigt, bei denen jemand die Reihenfolge der Aufzählung ändern könnte. Wie wenn: Die "enum {MALE, FEMALE}" wird zu einer "enum {UNKNOWN, FEMALE, MALE}", wenn jemand das Programm in der Zukunft erweitert, ohne zu wissen, dass man sich auf ordinal verlässt.

Gib Jon ein +1 für das Schreiben des gleichen Ich schrieb gerade.

    
Fredrik 17.10.2009 19:14
quelle

Tags und Links