Reemplazar valueof () y toString () en Java enum

122

Los valores en my enumson palabras que necesitan tener espacios en ellos, pero las enumeraciones no pueden tener espacios en sus valores, por lo que todo está agrupado. Quiero anular toString()para agregar estos espacios donde se lo indique.

También quiero que la enumeración proporcione la enumeración correcta cuando la uso valueOf()en la misma cadena a la que agregué los espacios.

Por ejemplo:

public enum RandomEnum
{
     StartHere,
     StopHere
}

La llamada toString()en RandomEnumcuyo valor es StartHeredevuelve cadena "Start Here". Llamada valueof()en esa misma cadena ( "Start Here") devuelve valor de enumeración StartHere.

¿Cómo puedo hacer esto?

WildBamaBoy
fuente
2
Agregue la etiqueta "Java" para obtener más comentarios / respuestas.
Java42
posible duplicado de Implementar toString en enumeraciones Java
Steve Chambers

Respuestas:

197

Puedes probar este código. Como no puede anular el valueOfmétodo, debe definir un método personalizado ( getEnumen el código de muestra a continuación) que devuelve el valor que necesita y cambia su cliente para utilizar este método.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private String value;

    RandomEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

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

    public static RandomEnum getEnum(String value) {
        for(RandomEnum v : values())
            if(v.getValue().equalsIgnoreCase(value)) return v;
        throw new IllegalArgumentException();
    }
}
Jugal Shah
fuente
44
getEnum podría acortarse si hace lo siguiente: v.getValue (). equals (value); el cheque nulo puede omitirse.
rtcarlson
También puede construir perezosamente un mapa y reutilizarlo, pero tenga cuidado con los problemas de subprocesamiento mientras lo construye.
Maarten Bodewes
11
no funciona en escenarios en los que no puede cambiar la llamada del método valueOf () a getValue (), por ejemplo, cuando define ciertos campos mediante anotaciones de Hibernate para asignar valores de enumeración
mmierins
Hasta que las Enums se arreglen en Java, @Bat tiene una solución más apropiada y amigable para OO. nombre () no debe ser final.
Andrew T Finnell
en lugar de para usted puede usar valueOf (RandomEnum.class, value);
Wojtek
22

Prueba esto, pero no estoy seguro de que funcione en todos lados :)

public enum MyEnum {
    A("Start There"),
    B("Start Here");

    MyEnum(String name) {
        try {
            Field fieldName = getClass().getSuperclass().getDeclaredField("name");
            fieldName.setAccessible(true);
            fieldName.set(this, name);
            fieldName.setAccessible(false);
        } catch (Exception e) {}
    }
}
Murciélago
fuente
18
¿Por qué soy el único al que ese tipo de cosas se ve malvado? Quiero decir que se ve realmente genial, pero alguien sin contexto que lea ese código probablemente sea como "WTF?".
Nicolas Guillaume
11
@NicolasGuillaume Ese es el tipo de código que, si se implementa, necesitaría desesperadamente un comentario que explicara por qué está allí y qué problema estaba tratando de resolver el programador. :-)
Ti Strga
9
No atrape la excepción genérica, ya que esto podría ser OutOfMemory o cualquier otra cosa
crusy
2
Esta es una idea terrible. No menos importante es la posibilidad de error silencioso sin indicación o recuperación. También ahora el valor del "nombre" y el valor simbólico en el código (por ejemplo, A, B) son diferentes. +2 por ser inteligente. -200 por ser terriblemente inteligente. No hay forma de que apruebe esta revisión de código.
pedorro
1
@pedorro Esto no parece ser tan terrible como pretendes que sea. Cualquiera que haya entendido que todo lo que hace el comité de Java no debe tomarse como Evangelio pasaría esto en una Revisión del Código. Es mucho peor tener que volver a escribir y luego mantener todos los métodos de la utilidad Enum, todo porque name () no se puede anular. En lugar de atrapar la excepción y tragarla, se debe lanzar una RuntimeException y esto está bien.
Andrew T Finnell
14

Puede usar un mapa estático en su enumeración que asigna cadenas a constantes de enumeración. Úselo en un método estático 'getEnum'. Esto evita la necesidad de recorrer las enumeraciones cada vez que desee obtener una de su valor de cadena.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private final String strVal;
    private RandomEnum(String strVal) {
        this.strVal = strVal;
    }

    public static RandomEnum getEnum(String strVal) {
        if(!strValMap.containsKey(strVal)) {
            throw new IllegalArgumentException("Unknown String Value: " + strVal);
        }
        return strValMap.get(strVal);
    }

    private static final Map<String, RandomEnum> strValMap;
    static {
        final Map<String, RandomEnum> tmpMap = Maps.newHashMap();
        for(final RandomEnum en : RandomEnum.values()) {
            tmpMap.put(en.strVal, en);
        }
        strValMap = ImmutableMap.copyOf(tmpMap);
    }

    @Override
    public String toString() {
        return strVal;
    }
}

Solo asegúrese de que la inicialización estática del mapa ocurra debajo de la declaración de las constantes enum.

Por cierto, ese tipo 'ImmutableMap' es de la API de guayaba de Google, y definitivamente lo recomiendo en casos como este.


EDITAR - Por los comentarios:

  1. Esta solución supone que cada valor de cadena asignado es único y no nulo. Dado que el creador de la enumeración puede controlar esto, y que la cadena corresponde al valor de enumeración único y no nulo, esto parece una restricción segura.
  2. Agregué el método 'toSTring ()' como se solicitó en la pregunta
pedorro
fuente
1
He recibido un par de votos negativos sobre esta respuesta. ¿A alguien le interesa ampliar por qué esta respuesta es mala? Tengo curiosidad por saber qué piensa la gente.
pedorro
Esto fallará para el escenario que tiene múltiples constantes enum con el mismo 'valor' similar RestartHere("replay"), ReplayHere("replay")o ningún 'valor' en absoluto PauseHere, WaitHere(es decir, enum tiene un constructor predeterminado similar aprivate RandomEnum() { this.strVal = null; }
sactiw
1
Debe usar ImmutableMap.Builder en lugar de crear un MutableMap para compilar + ImmutableMap :: copyOf.
Bryan Correll
Esto parece interesante, pero si la enumeración solo tiene unos pocos valores diferentes, no tardará mucho en iterar sobre ellos. Probablemente solo valga la pena si la enumeración tiene muchos valores.
Ben Thurley
13

¿Qué tal una implementación de Java 8? (nulo puede ser reemplazado por su enumeración predeterminada)

public static RandomEnum getEnum(String value) {
    List<RandomEnum> list = Arrays.asList(RandomEnum.values());
    return list.stream().filter(m -> m.value.equals(value)).findAny().orElse(null);
}

O podrías usar:

...findAny().orElseThrow(NotFoundException::new);
corlaez
fuente
2

No creo que vayas a obtener valueOf ("Comience aquí") para trabajar. Pero en cuanto a espacios ... intente lo siguiente ...

static private enum RandomEnum {
    R("Start There"), 
    G("Start Here"); 
    String value;
    RandomEnum(String s) {
        value = s;
    }
}

System.out.println(RandomEnum.G.value);
System.out.println(RandomEnum.valueOf("G").value);

Start Here
Start Here
Java42
fuente
2

La siguiente es una buena alternativa genérica a valueOf ()

public static RandomEnum getEnum(String value) {
  for (RandomEnum re : RandomEnum.values()) {
    if (re.description.compareTo(value) == 0) {
      return re;
    }
  }
  throw new IllegalArgumentException("Invalid RandomEnum value: " + value);
}
excepción
fuente
es el uso de compareTo()más elegante que equals()para cadenas?
Betlista
La elegancia no es un problema. Estoy de acuerdo en que .equals()funcionaría bien. Podría usar ==si quieres. (Aunque una buena razón para no hacerlo es si alguna vez reemplaza la enumeración con una clase.)
excepción
3
@exception NUNCA USE == para las comparaciones de cadenas, ya que hará una verificación de igualdad de objetos, mientras que lo que necesita es la verificación de igualdad de contenido y para eso use el método equals () de la clase String.
Sactiw
2

Todavía tiene una opción para implementar en su enumeración esto:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Andrey Lebedenko
fuente