Como posso procurar uma enumeração Java a partir do seu valor String?

164

Eu gostaria de procurar uma enumeração a partir do seu valor de string (ou possivelmente qualquer outro valor). Eu tentei o código a seguir, mas ele não permite estática nos inicializadores. Existe uma maneira simples?

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);
    }
};
peter.murray.rust
fonte
IIRC, que fornece um NPE porque a inicialização estática é feita de cima para baixo (ou seja, as constantes de enumeração no topo são construídas antes de chegar à stringMapinicialização). A solução usual é usar uma classe aninhada.
Tom Hawtin - tackline
Obrigado a todos por uma resposta tão rápida. (FWIW, não achei os Javadocs da Sun muito úteis para esse problema).
Peter.murray.rust 03/07/2009
É realmente uma questão de linguagem do que uma questão de biblioteca. No entanto, acho que os documentos da API são lidos mais do que o JLS (embora talvez não sejam os designers de linguagem), portanto, coisas como essa provavelmente devem ter mais destaque nos documentos java.lang.
Tom Hawtin # tackline 03/07/09

Respostas:

241

Use o valueOfmétodo criado automaticamente para cada Enum.

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

Para valores arbitrários, comece com:

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

Avance apenas mais tarde para a implementação do Mapa se o seu profiler solicitar.

Eu sei que ele está iterando sobre todos os valores, mas com apenas 3 valores enum dificilmente vale a pena qualquer outro esforço, na verdade, a menos que você tenha muitos valores, eu não me incomodaria com um mapa, será rápido o suficiente.

Gareth Davis
fonte
graças - e eu posso usar a conversão caso, se eu sei que os valores ainda são distintas
peter.murray.rust
Você pode acessar diretamente o membro da classe 'abbr' e, em vez de "v.abbr ()", pode usar "v.abbr.equals ...".
Amio.io 19/04
7
Além disso, para valores arbitrários (o segundo caso) considere lançar um em IllegalArgumentExceptionvez de retornar nulo ( ou seja, quando nenhuma correspondência for encontrada) - é assim que o JDK Enum.valueOf(..)faz isso de qualquer maneira.
Priidu Neemre
135

Você está perto. Para valores arbitrários, tente algo como o seguinte:

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);
    }
}
Lyle
fonte
4
Devido a problemas no carregador de classes, esse método não funcionará de maneira confiável. Não o recomendo e o vi falhar regularmente porque o mapa de pesquisa não está pronto antes de ser acessado. Você precisa colocar a "pesquisa" fora da enumeração ou em outra classe para que ela seja carregada primeiro.
Adam Gent
7
@ Adam Gent: Há um link abaixo para o JLS, onde essa construção exata é colocada como exemplo. Ele diz que a inicialização estática ocorre de cima para baixo: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena
3
Sim, o java moderno como o java 8 corrigiu o modelo de memória. Dito isto, os agentes de troca a quente, como o jrebel, ainda ficam confusos se você incorporar a inicialização por causa de referências circulares.
Adam Gent
@ Adam Gent: Hmmm. Eu aprendo algo novo todos os dias. [Falha silenciosamente para refatorar minhas enumerações com singletons de autoinicialização ...] Desculpe se esta é uma pergunta estúpida, mas isso é principalmente um problema de inicialização estática?
Selena
Eu nunca vi problemas com o bloco de código estático não sendo inicializado, mas só usei o Java 8 desde que comecei em Java, em 2015. Acabei de fazer um pequeno benchmark usando o JMH 1.22 e usando a HashMap<>para armazenar as enumeras. mais de 40% mais rápido que um loop de piso
cbaldan
21

com o Java 8, você pode conseguir desta maneira:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Lam Le
fonte
Eu lançaria uma exceção em vez de retornar null. Mas é também dependendo do caso de uso
alexander
12

@ A resposta de Lyle é bastante perigosa e vi que não funcionará particularmente se você fizer do enum uma classe interna estática. Em vez disso, usei algo assim, que carregará os mapas BootstrapSingleton antes das enumerações.

Editar isso não deve mais ser um problema com as JVMs modernas (JVM 1.6 ou superior), mas acho que ainda existem problemas com o JRebel, mas não tive a chance de testá-lo novamente .

Carregue-me primeiro:

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

Agora carregue-o no construtor 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 você tem uma enumeração interna, basta definir o Mapa acima da definição de enumeração e que (em teoria) deve ser carregado antes.

Adam Gent
fonte
3
Precisa definir "perigoso" e por que uma implementação de classe interna é importante. É mais um problema de definição estática de cima para baixo, mas há um código ativo em outra resposta relacionada, com um link para esta pergunta que usa um mapa estático na própria instância do Enum com um método "get" do valor definido pelo usuário para Tipo de enum. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague 15/16
2
@DarrellTeague, o problema original ocorreu devido a JVMs antigas (anteriores à 1.6) ou outras JVMs (IBMs) ou hotcode swappers (JRebel). O problema não deve ocorrer nas JVMs modernas, portanto, posso excluir minha resposta.
Adam Gent
É bom observar que, de fato, é possível devido a implementações personalizadas do compilador e otimizações de tempo de execução ... a ordem pode ser reequilibrada, resultando em um erro. No entanto, isso parece violar as especificações.
Darrell Teague
7

E você não pode usar valueOf () ?

Edit: Btw, não há nada impedindo você de usar static {} em uma enumeração.

Fredrik
fonte
Ou o sintético valueOfna classe enum, para que você não precise especificar a classe enum Class.
Tom Hawtin - tackline
Obviamente, ele simplesmente não tinha uma entrada no javadoc, por isso era difícil vincular.
11119 Fredrik
1
Você pode usar valueOf (), mas o nome deve corresponder idêntico ao definido na declaração de enum. Qualquer um dos dois métodos de pesquisa acima pode ser modificado para usar .equalsIgnoringCase () e possui um pouco mais de robustez ao erro.
@leonardo True. Se adicionar robustez ou apenas tolerância a falhas, é discutível. Na maioria dos casos, eu diria que é o último e, nesse caso, é melhor lidar com isso em outro lugar e ainda usar o valueOf () do Enum.
21412 Fredrik
4

Caso ajude outras pessoas, a opção que eu prefiro, que não está listada aqui, usa a funcionalidade Mapas do 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);
    }
}

Com o padrão que você pode usar null, você pode throw IllegalArgumentExceptionou fromStringpode retornar um Optional, qualquer que seja o comportamento de sua preferência.

Chad Befus
fonte
3

desde o java 8 você pode inicializar o mapa em uma única linha e sem bloco estático

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Adrian
fonte
+1 Excelente uso de fluxos e muito conciso sem sacrificar a legibilidade! Eu também nunca tinha visto esse uso Function.identity()antes, acho muito mais textualmente atraente do que e -> e.
Matsu Q.
0

Talvez, dê uma olhada nisso. Está funcionando para mim. O objetivo disso é pesquisar 'RED' com '/ red_color'. Declarar ae static mapcarregar os enums somente uma vez traria alguns benefícios de desempenho se os enums fossem muitos.

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

Por favor, coloque suas opiniões.

Midhun
fonte
-1

Você pode definir seu Enum como o seguinte código:

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

Veja o link a seguir para mais esclarecimentos

Vinay Sharma
fonte
-1

Se você deseja um valor padrão e não deseja criar mapas de pesquisa, pode criar um método estático para lidar com isso. Este exemplo também lida com pesquisas onde o nome esperado começaria com um número.

    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 você precisar dele com um valor secundário, basta criar o mapa de pesquisa primeiro, como em algumas das outras respostas.

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

}

Vahap Gencdal
fonte
-2

Você pode usar a Enum::valueOf()função conforme sugerido por Gareth Davis e Brad Mace acima, mas lembre-se de lidar com o IllegalArgumentExceptionque seria lançado se a string usada não estiver presente na enumeração.

Mayank Butpori
fonte