Come posso cercare un enum Java dal suo valore String?


164

Vorrei cercare un enum dal suo valore di stringa (o possibilmente qualsiasi altro valore). Ho provato il seguente codice ma non consente statici negli inizializzatori. C'è un modo semplice?

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, che fornisce un NPE perché l'inizializzazione statica viene eseguita dall'alto verso il basso (cioè le costanti di enum nella parte superiore sono costruite prima che scenda stringMapall'inizializzazione). La solita soluzione è utilizzare una classe nidificata.
Tom Hawtin - tackline

Grazie a tutti per una risposta così rapida. (FWIW non ho trovato Sun Javadocs molto utile per questo problema).
peter.murray.rust,

È davvero un problema di lingua rispetto a un problema di biblioteca. Tuttavia, penso che i documenti API siano letti più di JLS (anche se forse non dai designer di linguaggi), quindi cose come questa dovrebbero probabilmente avere maggiore risalto nei documenti java.lang.
Tom Hawtin - tackline

Risposte:


241

Utilizzare il valueOfmetodo che viene creato automaticamente per ogni Enum.

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

Per valori arbitrari iniziare con:

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

Passa successivamente all'implementazione della Mappa solo se il tuo profiler te lo dice.

So che sta iterando su tutti i valori, ma con solo 3 valori enum non vale quasi nessun altro sforzo, infatti a meno che tu non abbia molti valori non mi preoccuperei di una mappa, sarà abbastanza veloce.


grazie - e posso usare la conversione del caso se so che i valori sono ancora distinti
peter.murray.rust

Puoi accedere direttamente al membro della classe 'abbr', quindi invece di "v.abbr ()" puoi usare "v.abbr.equals ...".
Amio.io,

7
Inoltre, per valori arbitrari (il secondo caso) prendere in considerazione la possibilità di lanciare un valore IllegalArgumentExceptionanziché restituire un valore nullo ( ovvero quando non viene trovata alcuna corrispondenza): in questo modo JDK lo Enum.valueOf(..)fa comunque.
Priidu Neemre,

135

Sei vicino Per valori arbitrari, prova qualcosa di simile al seguente:

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
A causa di problemi con il classloader questo metodo non funzionerà in modo affidabile. Non lo consiglio e l'ho visto fallire regolarmente perché la mappa di ricerca non è pronta prima dell'accesso. È necessario posizionare la "ricerca" all'esterno dell'enum o in un'altra classe in modo che venga caricata per prima.
Adam Gent,

7
@Adam Gent: c'è un link in basso al JLS in cui questo costrutto esatto è posto come esempio. Dice che l'inizializzazione statica avviene dall'alto verso il basso: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena,

3
Sì, java moderno come java 8 ha corretto il modello di memoria. Detto questo, gli agenti hot swap come jrebel vengono ancora incasinati se si incorpora l'inizializzazione a causa di riferimenti circolari.
Adam Gent,

@Adam Gent: Hmmm. Imparo qualcosa di nuovo ogni giorno. [Scivolando piano per rifattare le mie enumerazioni con i singoli bootstrap ...] Scusa se questa è una domanda stupida, ma è principalmente un problema con l'inizializzazione statica?
Selena,

Non ho mai visto problemi con il blocco di codice statico non inizializzato, ma ho usato Java 8 da quando ho iniziato a Java, nel 2015. Ho appena fatto un piccolo benchmark usando JMH 1.22 e usando a HashMap<>per memorizzare gli enum si è dimostrato essere oltre il 40% più veloce di un loop foor.
cbaldan,

21

con Java 8 puoi ottenere in questo modo:

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

Vorrei fare un'eccezione piuttosto che tornare null. Ma dipende anche dal caso d'uso
alexander

12

La risposta di Lyle è piuttosto pericolosa e ho visto che non funziona in modo particolare se fai dell'enum una classe interna statica. Invece ho usato qualcosa del genere che caricherà le mappe BootstrapSingleton prima degli enum.

Modifica questo non dovrebbe più essere un problema con le moderne JVM (JVM 1.6 o successive) ma penso che ci siano ancora problemi con JRebel ma non ho avuto la possibilità di testarlo nuovamente .

Prima caricami:

   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>();
   }

Ora caricalo nel costruttore enum:

   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);
        }
    }

Se hai un enum interno puoi semplicemente definire la mappa sopra la definizione di enum e che (in teoria) dovrebbe essere caricata prima.


3
È necessario definire "pericoloso" e perché un'implementazione della classe interna conta. È più un problema di definizione statica dall'alto verso il basso ma esiste un codice funzionante in un'altra risposta correlata con collegamento a questa domanda che utilizza una mappa statica sull'istanza Enum stessa con un metodo "get" dal valore definito dall'utente a Tipo di enum. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague

2
@DarrellTeague il problema originale era dovuto a vecchie JVM (precedenti alla 1.6) o ad altre JVM (IBM) o hot swapper (JRebel). Il problema non dovrebbe verificarsi nelle JVM moderne, quindi posso semplicemente eliminare la mia risposta.
Adam Gent,

Va notato che in effetti è possibile a causa delle implementazioni del compilatore personalizzate e delle ottimizzazioni di runtime ... l'ordinamento potrebbe essere reinviato, con conseguente errore. Tuttavia, ciò sembrerebbe violare le specifiche.
Darrell Teague,

7

E non puoi usare valueOf () ?

Modifica: A proposito, non c'è nulla che ti impedisca di usare static {} in un enum.


O il sintetico valueOfsulla classe enum, quindi non è necessario specificare la classe enum Class.
Tom Hawtin - tackline

Ovviamente, non aveva una voce javadoc, quindi era difficile collegarsi.
Fredrik,

1
È possibile utilizzare valueOf () ma il nome deve corrispondere in modo identico a quello impostato nella dichiarazione enum. Uno dei due metodi di ricerca sopra può essere modificato per utilizzare .equalsIgnoringCase () e avere un po 'più di robustezza agli errori.

@leonardo True. Se aggiunge robustezza o solo la tolleranza agli errori è discutibile. Nella maggior parte dei casi, direi che è il secondo e in quel caso è meglio gestirlo altrove e usare comunque il valore di Enum ().
Fredrik,

4

Nel caso in cui aiuti gli altri, l'opzione che preferisco, che non è elencata qui, utilizza la funzionalità Mappe di 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);
    }
}

Con il valore predefinito che puoi utilizzare null, puoi throw IllegalArgumentExceptiono fromStringpotresti restituire un Optionalcomportamento qualunque tu preferisca.


3

da java 8 è possibile inizializzare la mappa in una sola riga e senza blocco statico

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

+1 Ottimo uso dei flussi e molto conciso senza sacrificare la leggibilità! Inoltre non avevo mai visto Function.identity()prima questo uso , lo trovo molto più interessante dal punto di vista testuale di e -> e.
Matsu Q.

0

Forse, dai un'occhiata a questo. Funziona per me. Lo scopo è cercare 'ROSSO' con '/ rosso_colore'. Dichiarare una static mape caricare le enums una sola volta porterebbe alcuni vantaggi in termini di prestazioni se le enums sono molte.

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);
        }
    }
}
}

Per favore, inserisci le tue opinioni.


-1

Puoi definire il tuo Enum come il seguente codice:

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();
   }
};

Vedi il link seguente per ulteriori chiarimenti


-1

Se si desidera un valore predefinito e non si desidera creare mappe di ricerca, è possibile creare un metodo statico per gestirlo. Questo esempio gestisce anche le ricerche in cui il nome previsto inizierà con un numero.

    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;
        }
    }

Se ne hai bisogno su un valore secondario, costruiresti prima la mappa di ricerca come in alcune delle altre risposte.


-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

Puoi usare la Enum::valueOf()funzione come suggerito da Gareth Davis e Brad Mace sopra, ma assicurati di gestire IllegalArgumentExceptionciò che verrebbe lanciato se la stringa usata non fosse presente nell'enum.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.