java - values - string a enum
Convenientemente mapa entre enum y int/String (16)
Cuando trabajo con variables / parámetros que solo pueden tomar un número finito de valores, trato de usar siempre la enum
de Java, como en
public enum BonusType {
MONTHLY, YEARLY, ONE_OFF
}
Mientras permanezca dentro de mi código, eso funciona bien. Sin embargo, a menudo necesito interactuar con otro código que use valores plain int
(o String
) para el mismo propósito, o necesito leer / escribir desde / hacia una base de datos donde los datos se almacenan como un número o cadena.
En ese caso, me gustaría tener una forma conveniente de asociar cada valor enum con un entero, de manera que pueda convertir ambos sentidos (en otras palabras, necesito una "enumeración reversible").
Pasar de enum a int es fácil:
public enum BonusType {
public final int id;
BonusType(int id) {
this.id = id;
}
MONTHLY(1), YEARLY(2), ONE_OFF(3);
}
Entonces puedo acceder al valor int como BonusType x = MONTHLY; int id = x.id;
BonusType x = MONTHLY; int id = x.id;
.
Sin embargo, no veo una buena manera de invertir, es decir, pasar de int a enum. Idealmente, algo como
BonusType bt = BonusType.getById(2);
Las únicas soluciones que pude encontrar son:
- Ponga un método de búsqueda en la enumeración, que usa
BonusType.values()
para llenar un mapa "int -> enum", luego lo almacena en caché y lo usa para las búsquedas. Funcionaría, pero tendría que copiar este método de forma idéntica en cada enumeración que uso :-(. - Ponga el método de búsqueda en una clase de utilidad estática. Entonces solo necesitaría un método de "búsqueda", pero tendría que jugar con la reflexión para que funcione en una enumeración arbitraria.
Ambos métodos parecen terriblemente incómodos para un problema tan simple (?).
¿Alguna otra idea / idea?
Un ejemplo de uso muy limpio de Enum inverso
Paso 1 Defina una interface
EnumConverter
public interface EnumConverter <E extends Enum<E> & EnumConverter<E>> {
public String convert();
E convert(String pKey);
}
Paso 2
Crea un nombre de clase ReverseEnumMap
import java.util.HashMap;
import java.util.Map;
public class ReverseEnumMap<V extends Enum<V> & EnumConverter<V>> {
private Map<String, V> map = new HashMap<String, V>();
public ReverseEnumMap(Class<V> valueType) {
for (V v : valueType.getEnumConstants()) {
map.put(v.convert(), v);
}
}
public V get(String pKey) {
return map.get(pKey);
}
}
Paso 3
Vaya a su clase Enum
e EnumConverter<ContentType>
con EnumConverter<ContentType>
y, por supuesto, anule los métodos de interfaz. También necesita inicializar un ReverseEnumMap estático.
public enum ContentType implements EnumConverter<ContentType> {
VIDEO("Video"), GAME("Game"), TEST("Test"), IMAGE("Image");
private static ReverseEnumMap<ContentType> map = new ReverseEnumMap<ContentType>(ContentType.class);
private final String mName;
ContentType(String pName) {
this.mName = pName;
}
String value() {
return this.mName;
}
@Override
public String convert() {
return this.mName;
}
@Override
public ContentType convert(String pKey) {
return map.get(pKey);
}
}
Etapa 4
Ahora crea un archivo de clase de Communication
y llámalo como un nuevo método para convertir un Enum
en String
y String
en Enum
. Acabo de poner el método principal para la explicación.
public class Communication<E extends Enum<E> & EnumConverter<E>> {
private final E enumSample;
public Communication(E enumSample) {
this.enumSample = enumSample;
}
public String resolveEnumToStringValue(E e) {
return e.convert();
}
public E resolveStringEnumConstant(String pName) {
return enumSample.convert(pName);
}
//Should not put main method here... just for explanation purpose.
public static void main(String... are) {
Communication<ContentType> comm = new Communication<ContentType>(ContentType.GAME);
comm.resolveEnumToStringValue(ContentType.GAME); //return Game
comm.resolveStringEnumConstant("Game"); //return GAME (Enum)
}
}
org.apache.commons.lang.enums.ValuedEnum;
Para ahorrarme la escritura de un montón de código repetitivo o código de duplicación para cada Enum, ValuedEnum
en ValuedEnum
lugar ValuedEnum
Apache Commons Lang.
Definición :
public class NRPEPacketType extends ValuedEnum {
public static final NRPEPacketType TYPE_QUERY = new NRPEPacketType( "TYPE_QUERY", 1);
public static final NRPEPacketType TYPE_RESPONSE = new NRPEPacketType( "TYPE_RESPONSE", 2);
protected NRPEPacketType(String name, int value) {
super(name, value);
}
}
Uso:
int -> ValuedEnum:
NRPEPacketType packetType =
(NRPEPacketType) EnumUtils.getEnum(NRPEPacketType.class, 1);
enum → int
yourEnum.ordinal()
int → enum
EnumType.values()[someInt]
String → enum
EnumType.valueOf(yourString)
enum → Cadena
yourEnum.name()
Una nota lateral:
Como correctamente indica, el ordinal()
puede ser "inestable" de una versión a otra. Esta es la razón exacta por la que siempre almaceno las constantes como cadenas en mis bases de datos. (¡En realidad, cuando uso MySql, los almaceno como enums de MySql !)
En aras de la exhaustividad, aquí hay un enfoque genérico para recuperar valores enum por índice de cualquier tipo de enumeración. Mi intención era hacer que el método se viera y se sintiera como Enum.valueOf (Class, String) . Fyi, copié este método desde here .
Los problemas relacionados con el índice (ya discutidos en profundidad aquí) aún se aplican.
/**
* Returns the {@link Enum} instance for a given ordinal.
* This method is the index based alternative
* to {@link Enum#valueOf(Class, String)}, which
* requires the name of an instance.
*
* @param <E> the enum type
* @param type the enum class object
* @param ordinal the index of the enum instance
* @throws IndexOutOfBoundsException if ordinal < 0 || ordinal >= enums.length
* @return the enum instance with the given ordinal
*/
public static <E extends Enum<E>> E valueOf(Class<E> type, int ordinal) {
Preconditions.checkNotNull(type, "Type");
final E[] enums = type.getEnumConstants();
Preconditions.checkElementIndex(ordinal, enums.length, "ordinal");
return enums[ordinal];
}
En este código, para búsqueda permanente e intensa, tenga memoria o proceso para usar, y selecciono la memoria, con matriz de convertidores como índice. Espero que sea útil
public enum Test{
VALUE_ONE(101, "Im value one"),
VALUE_TWO(215, "Im value two");
private final int number;
private final byte[] desc;
private final static int[] converter = new int[216];
static{
Test[] st = values();
for(int i=0;i<st.length;i++){
cv[st[i].number]=i;
}
}
Test(int value, byte[] description) {
this.number = value;
this.desc = description;
}
public int value() {
return this.number;
}
public byte[] description(){
return this.desc;
}
public static String description(int value) {
return values()[converter[rps]].desc;
}
public static Test fromValue(int value){
return values()[converter[rps]];
}
}
Encontré esto en la web, fue muy útil y simple de implementar. Esta solución NO fue hecha por mí
http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks
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);
}
}
Necesitaba algo diferente porque quería usar un enfoque genérico. Estoy leyendo las matrices enum hacia y desde bytes. Aquí es donde se me ocurre:
public interface EnumConverter {
public Number convert();
}
public class ByteArrayConverter {
@SuppressWarnings("unchecked")
public static Enum<?> convertToEnum(byte[] values, Class<?> fieldType, NumberSystem numberSystem) throws InvalidDataException {
if (values == null || values.length == 0) {
final String message = "The values parameter must contain the value";
throw new IllegalArgumentException(message);
}
if (!dtoFieldType.isEnum()) {
final String message = "dtoFieldType must be an Enum.";
throw new IllegalArgumentException(message);
}
if (!EnumConverter.class.isAssignableFrom(fieldType)) {
final String message = "fieldType must implement the EnumConverter interface.";
throw new IllegalArgumentException(message);
}
Enum<?> result = null;
Integer enumValue = (Integer) convertToType(values, Integer.class, numberSystem); // Our enum''s use Integer or Byte for the value field.
for (Object enumConstant : fieldType.getEnumConstants()) {
Number ev = ((EnumConverter) enumConstant).convert();
if (enumValue.equals(ev)) {
result = (Enum<?>) enumConstant;
break;
}
}
if (result == null) {
throw new EnumConstantNotPresentException((Class<? extends Enum>) fieldType, enumValue.toString());
}
return result;
}
public static byte[] convertEnumToBytes(Enum<?> value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
if (!(value instanceof EnumConverter)) {
final String message = "dtoFieldType must implement the EnumConverter interface.";
throw new IllegalArgumentException(message);
}
Number enumValue = ((EnumConverter) value).convert();
byte[] result = convertToBytes(enumValue, requiredLength, numberSystem);
return result;
}
public static Object convertToType(byte[] values, Class<?> type, NumberSystem numberSystem) throws InvalidDataException {
// some logic to convert the byte array supplied by the values param to an Object.
}
public static byte[] convertToBytes(Object value, int requiredLength, NumberSystem numberSystem) throws InvalidDataException {
// some logic to convert the Object supplied by the''value'' param to a byte array.
}
}
Ejemplo de enum:
public enum EnumIntegerMock implements EnumConverter {
VALUE0(0), VALUE1(1), VALUE2(2);
private final int value;
private EnumIntegerMock(int value) {
this.value = value;
}
public Integer convert() {
return value;
}
}
public enum EnumByteMock implements EnumConverter {
VALUE0(0), VALUE1(1), VALUE2(2);
private final byte value;
private EnumByteMock(int value) {
this.value = (byte) value;
}
public Byte convert() {
return value;
}
}
No estoy seguro de si es lo mismo en Java, pero los tipos enum en C también se asignan automáticamente a números enteros para que pueda usar el tipo o número entero para acceder a él. ¿Has intentado simplemente acceder a él con un número entero todavía?
Parece que la (s) respuesta (s) a esta pregunta están desactualizadas con el lanzamiento de Java 8.
- No use ordinal porque ordinal es inestable si persiste fuera de la JVM, como una base de datos.
- Es relativamente fácil crear un mapa estático con los valores clave.
public enum AccessLevel {
PRIVATE("private", 0),
PUBLIC("public", 1),
DEFAULT("default", 2);
AccessLevel(final String name, final int value) {
this.name = name;
this.value = value;
}
private final String name;
private final int value;
public String getName() {
return name;
}
public int getValue() {
return value;
}
static final Map<String, AccessLevel> names = Arrays.stream(AccessLevel.values())
.collect(Collectors.toMap(AccessLevel::getName, Function.identity()));
static final Map<Integer, AccessLevel> values = Arrays.stream(AccessLevel.values())
.collect(Collectors.toMap(AccessLevel::getValue, Function.identity()));
public static AccessLevel fromName(final String name) {
return names.get(name);
}
public static AccessLevel fromValue(final int value) {
return values.get(value);
}
}
Pregunta realmente genial :-) Usé una solución similar a la del Sr. Ferguson hace algún tiempo. Nuestra enumeración descompilada se ve así:
final class BonusType extends Enum
{
private BonusType(String s, int i, int id)
{
super(s, i);
this.id = id;
}
public static BonusType[] values()
{
BonusType abonustype[];
int i;
BonusType abonustype1[];
System.arraycopy(abonustype = ENUM$VALUES, 0, abonustype1 = new BonusType[i = abonustype.length], 0, i);
return abonustype1;
}
public static BonusType valueOf(String s)
{
return (BonusType)Enum.valueOf(BonusType, s);
}
public static final BonusType MONTHLY;
public static final BonusType YEARLY;
public static final BonusType ONE_OFF;
public final int id;
private static final BonusType ENUM$VALUES[];
static
{
MONTHLY = new BonusType("MONTHLY", 0, 1);
YEARLY = new BonusType("YEARLY", 1, 2);
ONE_OFF = new BonusType("ONE_OFF", 2, 3);
ENUM$VALUES = (new BonusType[] {
MONTHLY, YEARLY, ONE_OFF
});
}
}
Al ver esto, es evidente por qué ordinal()
es inestable. Es el i
en super(s, i);
. También soy pesimista de que se te ocurra una solución más elegante que las que ya has enumerado. Después de todas las enumeraciones son clases como cualquier clase final.
Solo porque la respuesta aceptada no es independiente:
Código de soporte:
public interface EnumWithCode<E extends Enum<E> & EnumWithCode<E>> {
public Integer getCode();
E fromCode(Integer code);
}
public class EnumWithCodeMap<V extends Enum<V> & EnumWithCode<V>> {
private final HashMap<Integer, V> _map = new HashMap<Integer, V>();
public EnumWithCodeMap(Class<V> valueType) {
for( V v : valueType.getEnumConstants() )
_map.put(v.getCode(), v);
}
public V get(Integer num) {
return _map.get(num);
}
}
Ejemplo de uso:
public enum State implements EnumWithCode<State> {
NOT_STARTED(0), STARTED(1), ENDED(2);
private static final EnumWithCodeMap<State> map = new EnumWithCodeMap<State>(
State.class);
private final int code;
private State(int code) {
this.code = code;
}
@Override
public Integer getCode() {
return code;
}
@Override
public State fromCode(Integer code) {
return map.get(code);
}
}
Tal vez podrías usar algo como
interface EnumWithId {
public int getId();
}
enum Foo implements EnumWithId {
...
}
Eso reduciría la necesidad de reflexión en su clase de utilidad.
Tanto el .ordinal()
como los values()[i]
son inestables ya que dependen del orden de las enumeraciones. Por lo tanto, si cambia el orden de las enumeraciones o agrega / borra algunas, su programa se romperá.
Aquí hay un método simple pero efectivo para mapear entre enum e int.
public enum Action {
ROTATE_RIGHT(0), ROTATE_LEFT(1), RIGHT(2), LEFT(3), UP(4), DOWN(5);
public final int id;
Action(int id) {
this.id = id;
}
public static Action get(int id){
for (Action a: Action.values()) {
if (a.id == id)
return a;
}
throw new IllegalArgumentException("Invalid id");
}
}
Aplicarlo a cuerdas no debería ser difícil.
Usa una interfaz para mostrar quién es el jefe.
public interface SleskeEnum {
int id();
SleskeEnum[] getValues();
}
public enum BonusType implements SleskeEnum {
MONTHLY(1), YEARLY(2), ONE_OFF(3);
public final int id;
BonusType(int id) {
this.id = id;
}
public SleskeEnum[] getValues() {
return values();
}
public int id() { return id; }
}
public class Utils {
public static SleskeEnum getById(SleskeEnum type, int id) {
for(SleskeEnum t : type.getValues())
if(t.id() == id) return t;
throw new IllegalArgumentException("BonusType does not accept id " + id);
}
public static void main(String[] args) {
BonusType shouldBeMonthly = (BonusType)getById(BonusType.MONTHLY,1);
System.out.println(shouldBeMonthly == BonusType.MONTHLY);
BonusType shouldBeMonthly2 = (BonusType)getById(BonusType.MONTHLY,1);
System.out.println(shouldBeMonthly2 == BonusType.YEARLY);
BonusType shouldBeYearly = (BonusType)getById(BonusType.MONTHLY,2);
System.out.println(shouldBeYearly == BonusType.YEARLY);
BonusType shouldBeOneOff = (BonusType)getById(BonusType.MONTHLY,3);
System.out.println(shouldBeOneOff == BonusType.ONE_OFF);
BonusType shouldException = (BonusType)getById(BonusType.MONTHLY,4);
}
}
Y el resultado:
C:/Documents and Settings/user/My Documents>java Utils
true
false
true
true
Exception in thread "main" java.lang.IllegalArgumentException: BonusType does not accept id 4
at Utils.getById(Utils.java:6)
at Utils.main(Utils.java:23)
C:/Documents and Settings/user/My Documents>
http://www.javaspecialists.co.za/archive/Issue113.html
La solución comienza de manera similar a la suya con un valor int como parte de la definición de enumeración. Luego continúa creando una utilidad de búsqueda basada en genéricos:
public class ReverseEnumMap<V extends Enum<V> & EnumConverter> {
private Map<Byte, V> map = new HashMap<Byte, V>();
public ReverseEnumMap(Class<V> valueType) {
for (V v : valueType.getEnumConstants()) {
map.put(v.convert(), v);
}
}
public V get(byte num) {
return map.get(num);
}
}
Esta solución es agradable y no requiere ''jugar con la reflexión'' porque se basa en el hecho de que todos los tipos enum heredan implícitamente la interfaz Enum.
Int -->String :
public enum Country {
US("US",0),
UK("UK",2),
DE("DE",1);
private static Map<Integer, String> domainToCountryMapping;
private String country;
private int domain;
private Country(String country,int domain){
this.country=country.toUpperCase();
this.domain=domain;
}
public String getCountry(){
return country;
}
public static String getCountry(String domain) {
if (domainToCountryMapping == null) {
initMapping();
}
if(domainToCountryMapping.get(domain)!=null){
return domainToCountryMapping.get(domain);
}else{
return "US";
}
}
private static void initMapping() {
domainToCountryMapping = new HashMap<Integer, String>();
for (Country s : values()) {
domainToCountryMapping.put(s.domain, s.country);
}
}