¿Cómo puedo buscar una enumeración de Java a partir de su valor de cadena?

164

Me gustaría buscar una enumeración de su valor de cadena (o posiblemente cualquier otro valor). He intentado el siguiente código pero no permite la estática en los inicializadores. ¿Hay una manera simple?

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
fuente
IIRC, que da un NPE porque la inicialización estática se realiza de arriba hacia abajo (es decir, las constantes de enumeración en la parte superior se construyen antes de que llegue a la stringMapinicialización). La solución habitual es usar una clase anidada.
Tom Hawtin - tackline el
Gracias a todos por tan rápida respuesta. (FWIW no encontré los Sun Javadocs muy útiles para este problema).
peter.murray.rust
Realmente es un problema de idioma que un problema de biblioteca. Sin embargo, creo que los documentos de la API se leen más que los JLS (aunque quizás no sean los diseñadores de idiomas), por lo que cosas como esta probablemente deberían tener más importancia en los documentos java.lang.
Tom Hawtin - tackline el

Respuestas:

241

Use el valueOfmétodo que se crea automáticamente para cada Enum.

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

Para valores arbitrarios comience con:

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

Avanza más tarde a la implementación del mapa si tu generador de perfiles te lo indica.

Sé que está iterando sobre todos los valores, pero con solo 3 valores de enumeración no vale la pena ningún otro esfuerzo, de hecho, a menos que tenga muchos valores, no me molestaría con un Mapa, será lo suficientemente rápido.

Gareth Davis
fuente
gracias - y puedo usar la conversión caso si sé que los valores siguen siendo distinta
peter.murray.rust
Puede acceder directamente al miembro de la clase 'abbr', por lo que en lugar de "v.abbr ()" puede usar "v.abbr.equals ...".
Amio.io
77
Además, para valores arbitrarios (el segundo caso) considere arrojar un IllegalArgumentExceptionnulo en lugar de devolver nulo ( es decir, cuando no se encuentra ninguna coincidencia), así es como lo hace el JDK Enum.valueOf(..).
Priidu Neemre
135

Estás cerca. Para valores arbitrarios, intente algo como lo siguiente:

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
fuente
44
Debido a problemas con el cargador de clases, este método no funcionará de manera confiable. No lo recomiendo y lo he visto fallar regularmente porque el mapa de búsqueda no está listo antes de acceder. Debe colocar la "búsqueda" fuera de la enumeración o en otra clase para que se cargue primero.
Adam Gent
77
@ Adam Gent: Hay un enlace a continuación al JLS donde esta construcción exacta se presenta como un ejemplo. Dice que la inicialización estática ocurre de arriba a abajo: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Selena
3
Sí, Java moderno como Java 8 ha reparado el modelo de memoria. Dicho esto, los agentes de intercambio en caliente como jrebel todavía se confunden si incrusta la inicialización debido a referencias circulares.
Adam Gent
@ Adam Gent: Hmmm. Aprendo algo nuevo cada día. [Silenciosamente escabulléndome para refactorizar mis enumeraciones con bootstrap singletons ...] Lo siento si esta es una pregunta estúpida, pero ¿es principalmente un problema con la inicialización estática?
Selena
Nunca he visto problemas con el bloque de código estático que no se inicializa, pero solo he usado Java 8 desde que comencé en Java, en 2015. Solo hice un pequeño punto de referencia usando JMH 1.22 y usando a HashMap<>para almacenar las enumeraciones demostró ser más del 40% más rápido que un bucle forzado.
cbaldan
21

con Java 8 puedes lograr de esta manera:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Lam Le
fuente
Lanzaría una excepción en lugar de regresar null. Pero también depende del caso de uso
alexander
12

La respuesta de @ Lyle es bastante peligrosa y he visto que no funciona particularmente si hace que la enumeración sea una clase interna estática. En cambio, he usado algo como esto que cargará los mapas BootstrapSingleton antes de las enumeraciones.

Editar esto ya no debería ser un problema con las JVM modernas (JVM 1.6 o superior), pero creo que todavía hay problemas con JRebel, pero no he tenido la oportunidad de volver a probarlo .

Cargame primero:

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

Ahora cárguelo en el constructor 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);
        }
    }

Si tiene una enumeración interna, puede definir el Mapa sobre la definición de enumeración y eso (en teoría) debería cargarse antes.

Adam Gent
fuente
3
Necesita definir "peligroso" y por qué es importante una implementación de clase interna. Es más un problema de definición estática de arriba a abajo, pero hay un código de trabajo en otra respuesta relacionada con un enlace a esta pregunta que usa un Mapa estático en la instancia de Enum con un método "get" desde el valor definido por el usuario hasta Tipo de enumeración. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Darrell Teague
2
@DarrellTeague, el problema original se debió a antiguas JVM (anteriores a 1.6) u otras JVM (IBM) o intercambiadores de códigos activos (JRebel). El problema no debería suceder en las JVM modernas, por lo que puedo eliminar mi respuesta.
Adam Gent
Es bueno tener en cuenta que, de hecho, es posible debido a las implementaciones de compiladores personalizados y las optimizaciones de tiempo de ejecución ... el orden podría volver a secuenciarse, lo que da como resultado un error. Sin embargo, esto parecería violar la especificación.
Darrell Teague
7

¿Y no puedes usar valueOf () ?

Editar: Por cierto, no hay nada que te impida usar static {} en una enumeración.

Fredrik
fuente
O el sintético valueOfen la clase enum, por lo que no necesita especificar la clase enum Class.
Tom Hawtin - tackline
Por supuesto, simplemente no tenía una entrada de javadoc, por lo que era difícil vincularlo.
Fredrik el
1
Puede usar valueOf () pero el nombre debe coincidir de manera idéntica a ese conjunto en la declaración de enumeración. Cualquiera de los dos métodos de búsqueda anteriores se puede modificar para usar .equalsIgnoringCase () y tener un poco más de solidez al error.
@leonardo True. Si agrega robustez o simplemente tolerancia a fallas es discutible. En la mayoría de los casos, diría que es lo último y, en ese caso, es mejor manejarlo en otro lugar y seguir utilizando el valor de Enum ().
Fredrik
4

En caso de que ayude a otros, la opción que prefiero, que no se encuentra aquí, utiliza la funcionalidad de Mapas de 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 el valor predeterminado que puede usar null, puede throw IllegalArgumentExceptiono fromStringpuede devolver un Optionalcomportamiento que prefiera.

Chad Befus
fuente
3

desde java 8 puede inicializar el mapa en una sola línea y sin bloque estático

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Adrian
fuente
+1 Excelente uso de transmisiones, y muy conciso sin sacrificar la legibilidad. Tampoco había visto este uso Function.identity()antes, me parece mucho más atractivo textualmente que e -> e.
Matsu Q.
0

Quizás, eche un vistazo a esto. Me está funcionando. El propósito de esto es buscar 'RED' con '/ red_color'. Declarar ay static mapcargar los enums solo una vez traerá algunos beneficios de rendimiento si los enums son muchos.

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, ponga sus opiniones.

Midhun
fuente
-1

Puede definir su Enum como el siguiente 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();
   }
};

Consulte el siguiente enlace para obtener más aclaraciones.

Vinay Sharma
fuente
-1

Si desea un valor predeterminado y no desea crear mapas de búsqueda, puede crear un método estático para manejarlo. Este ejemplo también maneja búsquedas donde el nombre esperado comenzaría con un 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;
        }
    }

Si lo necesita en un valor secundario, simplemente construirá el mapa de búsqueda primero como en algunas de las otras respuestas.

ScrappyDev
fuente
-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
fuente
-2

Puede usar la Enum::valueOf()función sugerida por Gareth Davis y Brad Mace arriba, pero asegúrese de manejar la IllegalArgumentExceptionque se lanzaría si la cadena utilizada no está presente en la enumeración.

Mayank Butpori
fuente