Wie kann ich eine Java-Enumeration anhand ihres String-Werts nachschlagen?


164

Ich möchte eine Aufzählung anhand ihres Zeichenfolgenwerts (oder möglicherweise eines anderen Werts) nachschlagen. Ich habe den folgenden Code ausprobiert, aber er erlaubt keine statische Aufladung in Initialisierern. Gibt es einen einfachen Weg?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};

IIRC, das eine NPE ergibt, weil die statische Initialisierung von oben nach unten erfolgt (dh die Enum-Konstanten oben werden konstruiert, bevor sie zur stringMapInitialisierung führen). Die übliche Lösung besteht darin, eine verschachtelte Klasse zu verwenden.
Tom Hawtin - Tackline

Vielen Dank an alle für diese schnelle Antwort. (FWIW Ich fand die Sun Javadocs für dieses Problem nicht sehr nützlich).
Peter.murray.rust

Es ist wirklich ein Sprachproblem als ein Bibliotheksproblem. Ich denke jedoch, dass die API-Dokumente mehr gelesen werden als die JLS (obwohl möglicherweise nicht von Sprachdesignern), daher sollten solche Dinge in den java.lang-Dokumenten wahrscheinlich eine größere Bedeutung haben.
Tom Hawtin - Tackline

Antworten:


241

Verwenden Sie die valueOfMethode, die automatisch für jede Aufzählung erstellt wird.

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

Für beliebige Werte beginnen Sie mit:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

Fahren Sie erst später mit der Map-Implementierung fort, wenn Sie von Ihrem Profiler dazu aufgefordert werden.

Ich weiß, dass es über alle Werte iteriert, aber mit nur 3 Aufzählungswerten ist es kaum eine andere Anstrengung wert. Wenn Sie nicht viele Werte haben, würde ich mich nicht um eine Karte kümmern, wird es schnell genug sein.


danke - und ich kann die
Fallkonvertierung

Sie können direkt auf das Klassenmitglied 'abbr' zugreifen, sodass Sie anstelle von "v.abbr ()" "v.abbr.equals ..." verwenden können.
Amio.io

7
Bei beliebigen Werten (im zweiten Fall) sollten Sie eine IllegalArgumentExceptionNull auslösen, anstatt eine Null zurückzugeben ( dh wenn keine Übereinstimmung gefunden wird). So Enum.valueOf(..)macht es das JDK jedenfalls.
Priidu Neemre

135

Du bist nah dran. Versuchen Sie für beliebige Werte Folgendes:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}

4
Aufgrund von Classloader-Problemen funktioniert diese Methode nicht zuverlässig. Ich empfehle es nicht und habe festgestellt, dass es regelmäßig fehlschlägt, da die Suchkarte vor dem Zugriff noch nicht fertig ist. Sie müssen die "Suche" außerhalb der Aufzählung oder in einer anderen Klasse platzieren, damit sie zuerst geladen wird.
Adam Gent

7
@Adam Gent: Es gibt unten einen Link zum JLS, wo genau dieses Konstrukt als Beispiel dient. Es heißt, dass die statische Initialisierung von oben nach unten erfolgt: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena

3
Ja, modernes Java wie Java 8 hat das Speichermodell repariert. Davon abgesehen werden Hot-Swap-Agenten wie jrebel immer noch durcheinander gebracht, wenn Sie die Initialisierung aufgrund von Zirkelverweisen einbetten.
Adam Gent

@ Adam Gent: Hmmm. Ich lerne jeden Tag etwas Neues. [Leise abschleichen, um meine Enums mit Bootstrap-Singletons zu überarbeiten ...] Entschuldigung, wenn dies eine dumme Frage ist, aber ist dies hauptsächlich ein Problem mit der statischen Initialisierung?
Selena

Ich habe noch nie Probleme mit dem nicht initialisierten statischen Codeblock gesehen, aber ich habe Java 8 erst verwendet, seit ich 2015 in Java angefangen habe. Ich habe nur einen kleinen Benchmark mit JMH 1.22 durchgeführt und a HashMap<>zum Speichern der Aufzählungen verwendet über 40% schneller als eine Bodenschleife.
Cbaldan

21

Mit Java 8 können Sie Folgendes erreichen:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}

Ich würde eine Ausnahme werfen, anstatt zurückzukehren null. Aber es hängt auch vom Anwendungsfall ab
Alexander

12

@ Lyles Antwort ist ziemlich gefährlich und ich habe gesehen, dass es nicht besonders funktioniert, wenn Sie die Aufzählung zu einer statischen inneren Klasse machen. Stattdessen habe ich so etwas verwendet, das die BootstrapSingleton-Maps vor den Aufzählungen lädt.

Bearbeiten Dies sollte mit modernen JVMs (JVM 1.6 oder höher) kein Problem mehr sein, aber ich denke, es gibt immer noch Probleme mit JRebel, aber ich hatte keine Chance, es erneut zu testen .

Lade mich zuerst:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

Laden Sie es nun in den Enum-Konstruktor:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

Wenn Sie eine innere Aufzählung haben, können Sie einfach die Karte über der Aufzählungsdefinition definieren und diese sollte (theoretisch) vorher geladen werden.


3
Notwendigkeit, "gefährlich" zu definieren und warum eine Implementierung innerhalb der Klasse wichtig ist. Es handelt sich eher um ein Problem der statischen Definition von oben nach unten, aber es gibt Arbeitscode in einer anderen verwandten Antwort mit einem Link zurück zu dieser Frage, der eine statische Zuordnung auf der Enum-Instanz selbst mit einer "get" -Methode von einem benutzerdefinierten Wert bis verwendet Aufzählungstyp. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague

2
@DarrellTeague Das ursprüngliche Problem war auf alte JVMs (vor 1.6) oder andere JVMs (IBMs) oder Hotcode-Swapper (JRebel) zurückzuführen. Das Problem sollte in modernen JVMs nicht auftreten, daher kann ich meine Antwort einfach löschen.
Adam Gent

Gut zu beachten, dass dies aufgrund von benutzerdefinierten Compiler-Implementierungen und Laufzeitoptimierungen tatsächlich möglich ist. Die Reihenfolge kann neu geordnet werden, was zu einem Fehler führt. Dies scheint jedoch gegen die Spezifikation zu verstoßen.
Darrell Teague

7

Und Sie können valueOf () nicht verwenden ?

Bearbeiten: Übrigens hindert Sie nichts daran, statisches {} in einer Aufzählung zu verwenden.


Oder die synthetische valueOfAufzählungsklasse, sodass Sie die Aufzählungsklasse nicht angeben müssen Class.
Tom Hawtin - Tackline

Natürlich gab es keinen Javadoc-Eintrag, daher war es schwierig, einen Link zu erstellen.
Fredrik

1
Sie können valueOf () verwenden, aber der Name muss mit dem in der Enum-Deklaration festgelegten Namen übereinstimmen. Jede der beiden oben genannten Suchmethoden kann so geändert werden, dass sie .equalsIgnoringCase () verwendet und etwas robuster ist.

@leonardo Stimmt. Ob es Robustheit oder nur Fehlertoleranz hinzufügt, ist umstritten. In den meisten Fällen würde ich sagen, dass es das letztere ist, und in diesem Fall ist es besser, es anderswo zu behandeln und trotzdem Enums valueOf () zu verwenden.
Fredrik

4

Falls es anderen hilft, verwendet die von mir bevorzugte Option, die hier nicht aufgeführt ist, die Kartenfunktion von Guava :

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

    public String getValue() {
        return this.value;
    }

    private static ImmutableMap<String, Verbosity> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(Verbosity.values()), Verbosity::getValue);

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

Mit der Standardeinstellung, die Sie verwenden können null, können throw IllegalArgumentExceptionoder können Sie fromStringein Optionalbeliebiges Verhalten zurückgeben.


3

Seit Java 8 können Sie die Karte in einer einzigen Zeile und ohne statischen Block initialisieren

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));

+1 Hervorragende Verwendung von Streams und sehr präzise, ​​ohne die Lesbarkeit zu beeinträchtigen! Ich hatte diese Verwendung auch noch nie gesehen Function.identity(), ich finde sie textlich viel ansprechender als e -> e.
Matsu Q.

0

Vielleicht werfen Sie einen Blick darauf. Es funktioniert für mich. Der Zweck ist, 'RED' mit '/ red_color' nachzuschlagen. Das Deklarieren von a static mapund das enumeinmalige Laden des s würde einige Leistungsvorteile bringen, wenn enumes viele s gibt.

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

Bitte geben Sie Ihre Meinung ein.


-1

Sie können Ihre Aufzählung wie folgt definieren:

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

   public int getValue()
   {
     return this.value;
   } 

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

Weitere Informationen finden Sie unter folgendem Link


-1

Wenn Sie einen Standardwert möchten und keine Lookup-Maps erstellen möchten, können Sie eine statische Methode erstellen, um dies zu handhaben. In diesem Beispiel werden auch Suchvorgänge behandelt, bei denen der erwartete Name mit einer Zahl beginnen würde.

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

Wenn Sie es für einen sekundären Wert benötigen, erstellen Sie zuerst die Lookup-Map wie in einigen anderen Antworten.


-1
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}}


-2

Sie können die Enum::valueOf()von Gareth Davis & Brad Mace oben vorgeschlagene Funktion verwenden, aber stellen Sie sicher, dass Sie mit der Funktion umgehen IllegalArgumentException, die ausgelöst wird, wenn die verwendete Zeichenfolge nicht in der Aufzählung vorhanden ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.