Ich habe in einem Blog gesehen, dass das Folgende eine vernünftige Möglichkeit ist, eine "Reverse-Lookup" mit der getCode(int)
in einer Java-Enumeration durchzuführen:
public enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private static final Map<Integer,Status> lookup
= new HashMap<Integer,Status>();
static {
for(Status s : EnumSet.allOf(Status.class))
lookup.put(s.getCode(), s);
}
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
public static Status get(int code) {
return lookup.get(code);
}
}
Für mich sehen sowohl die statische Karte als auch der statische Initialisierer wie eine schlechte Idee aus, und mein erster Gedanke wäre, die Suche so zu codieren:
public enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
public static Status get(int code) {
for(Status s : values()) {
if(s.code == code) return s;
}
return null;
}
}
Gibt es offensichtliche Probleme mit beiden Methoden und gibt es eine empfohlene Möglichkeit, diese Art der Suche zu implementieren?
java
enums
static-initializer
Armand
quelle
quelle
for(Status s : values()) lookup.put(s.code, s);
Enum.valueOf()
? Können Sie Strings nicht speichern?Enum.valueOf()
(mind Kapitalisierung obwohl) , aber ziemlich oft haben Sie nur ein Byte oder eine Nummer zu beginnen habe. Und bitte: Wenn eine Zeichenfolge nicht benötigt wird, lassen Sie sie weg und suchen Sie nach "Stringing Typed Coding Horror", wenn Sie wissen möchten, warum. Grundsätzlich sollten Sie sich ständig fragen: Wenn ich eine Zeichenfolge erhalte, weiß ich, was darin enthalten ist? Es enthält weit mehr Zustand als eine ganze Zahl oder in der Tat eine Aufzählung, und die Erhöhung des Zustands ist schlecht .Antworten:
Maps.uniqueIndex aus Googles Guave ist sehr praktisch, um Lookup-Karten zu erstellen.
Update: Hier ist ein Beispiel für die Verwendung
Maps.uniqueIndex
mit Java 8:public enum MyEnum { A(0), B(1), C(2); private static final Map<Integer, MyEnum> LOOKUP = Maps.uniqueIndex( Arrays.asList(MyEnum.values()), MyEnum::getStatus ); private final int status; MyEnum(int status) { this.status = status; } public int getStatus() { return status; } @Nullable public static MyEnum fromStatus(int status) { return LOOKUP.get(status); } }
quelle
static
Klasseninitialisierer. Weiß jemand, ob es andere Vorteile gegenüber einem bestimmtenMap
von Java hat (nur ein statischer Initialisierer für einen feldspezifischen Initialisierer loszuwerden, reicht für mich nicht aus, um eine Bibliothek in meinen Klassenpfad aufzunehmen, selbst wenn diese Bibliothek Guava ist).LOOKUP = stream(values()).collect(toMap(MyEnum::getStatus, x -> x));
.Obwohl der Overhead höher ist, ist die statische Karte gut, da sie eine zeitlich konstante Suche nach bietet
code
. Die Suchzeit Ihrer Implementierung erhöht sich linear mit der Anzahl der Elemente in der Aufzählung. Bei kleinen Aufzählungen trägt dies einfach nicht wesentlich dazu bei.Ein Problem bei beiden Implementierungen (und wohl auch bei Java-Enums im Allgemeinen) ist, dass es wirklich einen versteckten zusätzlichen Wert gibt, den a
Status
annehmen kann :null
. Abhängig von den Regeln der Geschäftslogik kann es sinnvoll sein, einen tatsächlichen Aufzählungswert zurückzugeben oder einen zu werfenException
, wenn die Suche "fehlschlägt".quelle
O(n)
in der Größe der Aufzählung. Dies ist alles Pedanterie, da es unwahrscheinlich ist, dass die Aufzählung groß ist.O(n)
bedeutet nicht, dass ern
sich zur Laufzeit ändern kann . Was wäre, wenn dies eine analoge Suche in einer unveränderlichen (daher zur Laufzeit festgelegten) Liste durchführen würde? Das wäre absolut einO(n)
Algorithmus, won
ist die Größe der Liste.Hier ist eine Alternative, die vielleicht sogar etwas schneller ist:
public enum Status { WAITING(0), READY(1), SKIPPED(-1), COMPLETED(5); private int code; private Status(int code) { this.code = code; } public int getCode() { return code; } public static Status get(int code) { switch(code) { case 0: return WAITING; case 1: return READY; case -1: return SKIPPED; case 5: return COMPLETED; } return null; } }
Dies ist natürlich nicht wirklich wartbar, wenn Sie später weitere Konstanten hinzufügen möchten.
quelle
values()
Array vorher nicht erstellen und füllen (was allein diese Variante ergeben würdeO(n)
). Natürlich ist die Methode jetzt länger, daher dauert das Laden länger.case WAITING.code
ist eine nette Idee, aber ich befürchte, dass dies keine Konstante zur Kompilierungszeit ist.Offensichtlich bietet die Karte eine konstante Zeitsuche, während die Schleife dies nicht tut. In einer typischen Aufzählung mit wenigen Werten sehe ich kein Problem mit der Traversal-Suche.
quelle
Hier ist eine Java 8-Alternative (mit Unit-Test):
// DictionarySupport.java : import org.apache.commons.collections4.Factory; import org.apache.commons.collections4.map.LazyMap; import java.util.HashMap; import java.util.Map; public interface DictionarySupport<T extends Enum<T>> { @SuppressWarnings("unchecked") Map<Class<?>, Map<String, Object>> byCodeMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new); @SuppressWarnings("unchecked") Map<Class<?>, Map<Object, String>> byEnumMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new); default void init(String code) { byCodeMap.get(this.getClass()).put(code, this); byEnumMap.get(this.getClass()).put(this, code) ; } static <T extends Enum<T>> T getByCode(Class<T> clazz, String code) { clazz.getEnumConstants(); return (T) byCodeMap.get(clazz).get(code); } default <T extends Enum<T>> String getCode() { return byEnumMap.get(this.getClass()).get(this); } } // Dictionary 1: public enum Dictionary1 implements DictionarySupport<Dictionary1> { VALUE1("code1"), VALUE2("code2"); private Dictionary1(String code) { init(code); } } // Dictionary 2: public enum Dictionary2 implements DictionarySupport<Dictionary2> { VALUE1("code1"), VALUE2("code2"); private Dictionary2(String code) { init(code); } } // DictionarySupportTest.java: import org.testng.annotations.Test; import static org.fest.assertions.api.Assertions.assertThat; public class DictionarySupportTest { @Test public void teetSlownikSupport() { assertThat(getByCode(Dictionary1.class, "code1")).isEqualTo(Dictionary1.VALUE1); assertThat(Dictionary1.VALUE1.getCode()).isEqualTo("code1"); assertThat(getByCode(Dictionary1.class, "code2")).isEqualTo(Dictionary1.VALUE2); assertThat(Dictionary1.VALUE2.getCode()).isEqualTo("code2"); assertThat(getByCode(Dictionary2.class, "code1")).isEqualTo(Dictionary2.VALUE1); assertThat(Dictionary2.VALUE1.getCode()).isEqualTo("code1"); assertThat(getByCode(Dictionary2.class, "code2")).isEqualTo(Dictionary2.VALUE2); assertThat(Dictionary2.VALUE2.getCode()).isEqualTo("code2"); } }
quelle
In Java 8 würde ich einfach die folgende Factory-Methode zu Ihrer Aufzählung hinzufügen und die Lookup-Map überspringen.
public static Optional<Status> of(int value) { return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst(); }
quelle
values()
(dies ist an sich sehr langsam. Wenn Sie also viel tun, können Sie dies erheblich beschleunigen, indem Sie das Array einmal zwischenspeichern und wiederverwenden). 2. Sie verwenden Streams (die in Leistungs- / Benchmark-Tests im Allgemeinen viel langsamer testen als eine einfache for-Schleife).Beide Wege sind vollkommen gültig. Und sie haben technisch die gleiche Big-Oh-Laufzeit.
Wenn Sie jedoch zuerst alle Werte in einer Karte speichern, sparen Sie die Zeit, die erforderlich ist, um das Set jedes Mal zu durchlaufen, wenn Sie eine Suche durchführen möchten. Daher denke ich, dass die statische Karte und der Initialisierer ein etwas besserer Weg sind.
quelle
O(1)
:-)HashMap
. Auf der anderen Seite ist n 4 ...