Convertir de enum ordinal a enum type

316

Tengo el tipo de enumeración ReportTypeEnumque se pasa entre los métodos en todas mis clases, pero luego necesito pasar esto en la URL, así que uso el método ordinal para obtener el valor int. Después de obtenerlo en mi otra página JSP, necesito convertirlo de nuevo a un ReportTypeEnumpara poder continuar pasándolo.

¿Cómo puedo convertir ordinal a la ReportTypeEnum?

Usando Java 6 SE.

Lennie
fuente
1
No hay Java 6 EE, hasta ahora (AFAIK). Hay Java SE 6 y Java EE 5.
Hosam Aly

Respuestas:

632

Para convertir un ordinal en su representación enum, es posible que desee hacer esto:

ReportTypeEnum value = ReportTypeEnum.values()[ordinal];

Tenga en cuenta los límites de la matriz.

Tenga en cuenta que cada llamada a values()devuelve una matriz recién clonada que podría afectar el rendimiento de manera negativa. Es posible que desee almacenar en caché la matriz si se va a llamar con frecuencia.

Ejemplo de código sobre cómo almacenar en cachévalues() .


Esta respuesta fue editada para incluir la retroalimentación dada dentro de los comentarios

Joachim Sauer
fuente
Implementé esta solución y no me funciona. Devuelve el valor ordinal no se garantiza que coincida con el orden en que se agregan los tipos enumerados. No sé si eso es lo que esta respuesta defiende, pero de todos modos quería advertir a la gente
IcedDante
@IcesDante: se garantiza que el ordinal corresponderá al orden de los valores de enumeración en la fuente. Si observa un comportamiento diferente, entonces algo más debe estar mal. Sin embargo, mi respuesta anterior es subóptima por todas las razones expuestas en las otras respuestas.
Joachim Sauer
@JoachimSauer Quizás IcedDante significa que el ordinal puede no coincidir si fue producido y guardado por una versión anterior de la fuente, en la que los valores de enumeración estaban en un orden diferente.
LarsH
137

Es casi seguro que sea una mala idea . Ciertamente, si el ordinal es de facto persistente (por ejemplo, porque alguien ha marcado la URL), significa que siempre debe preservar el enumorden en el futuro, lo que puede no ser obvio para los mantenedores de código en el futuro.

¿Por qué no codificar el enumuso myEnumValue.name()(y decodificar mediante ReportTypeEnum.valueOf(s)) en su lugar?

oxbow_lakes
fuente
24
¿Qué sucede si cambia el nombre de la enumeración (pero mantiene el orden)?
Arne Evertsson
66
@Arne: creo que esto es mucho menos probable que una persona inexperta que venga y agregue un valueinicio o su posición alfabética / lógica correcta. (Por lógico quiero decir, por ejemplo, los TimeUnitvalores tienen una posición lógica)
oxbow_lakes
77
Ciertamente prefiero forzar el orden de las enumeraciones en lugar del nombre de mi enumeración ... es por eso que prefiero almacenar el ordinal en lugar del nombre de la enumeración en la base de datos. Además, es mejor usar la manipulación int en lugar de String ...
8
Estoy de acuerdo. En una API pública, cambiar el nombre de una Enum rompería la compatibilidad con versiones anteriores, pero cambiar el orden no lo haría. Por esa razón, tiene más sentido usar el nombre como su "clave"
Noel
3
Almacenar el ordinal hace que sea más fácil traducir sus ideas a otros idiomas. ¿Qué pasa si necesita escribir algún componente en C?
QED
94

Si voy a usar values()mucho:

enum Suit {
   Hearts, Diamonds, Spades, Clubs;
   public static final Suit values[] = values();
}

Mientras tanto donde sea.java:

Suit suit = Suit.values[ordinal];

Cuida los límites de tu matriz.

QED
fuente
3
+1 esta es, de lejos, la mejor solución en mi humilde opinión porque uno puede pasar ordinales, especialmente en android.os.Message.
likejudo
99
Esto es muy preocupante porque las matrices son mutables . A pesar de que los valores [] son ​​finales, no evita Suit.values[0] = Suit.Diamonds;en algún lugar de su código. Idealmente, eso nunca sucederá, pero el principio general de no exponer campos mutables aún se mantiene. Para este enfoque, considere usar en su lugar Collections.unmodifiableListo similares.
Mshnik
¿Qué tal? Valores privados estáticos finales de Traje [] = valores (); public static Suit [] getValues ​​() {valores de retorno; }
Pratyush
44
@Pratyush que haría que la variable de matriz sea inmutable, pero no su contenido. Todavía podría hacer getValues ​​() [0] = somethingElse;
Calabacin
De acuerdo, por lo tanto, el punto no es exponer Suit values[]directa o indirectamente (cómo se mencionó getValues()), trabajo con un método público donde el ordinalvalor debe enviarse como un argumento y devolver la Suitrepresentación Suit values[]. El punto aquí (el mosaico de la pregunta desde el principio) fue crear un tipo de enumeración a partir de un enum ordinal
Manuel Jordan
13

Estoy de acuerdo con la mayoría de las personas en que usar ordinal es probablemente una mala idea. Por lo general, resuelvo este problema dándole a la enumeración un constructor privado que puede tomar, por ejemplo, un valor DB y luego crear una fromDbValuefunción estática similar a la de la respuesta de Jan.

public enum ReportTypeEnum {
    R1(1),
    R2(2),
    R3(3),
    R4(4),
    R5(5),
    R6(6),
    R7(7),
    R8(8);

    private static Logger log = LoggerFactory.getLogger(ReportEnumType.class);  
    private static Map<Integer, ReportTypeEnum> lookup;
    private Integer dbValue;

    private ReportTypeEnum(Integer dbValue) {
        this.dbValue = dbValue;
    }


    static {
        try {
            ReportTypeEnum[] vals = ReportTypeEnum.values();
            lookup = new HashMap<Integer, ReportTypeEnum>(vals.length);

            for (ReportTypeEnum  rpt: vals)
                lookup.put(rpt.getDbValue(), rpt);
         }
         catch (Exception e) {
             // Careful, if any exception is thrown out of a static block, the class
             // won't be initialized
             log.error("Unexpected exception initializing " + ReportTypeEnum.class, e);
         }
    }

    public static ReportTypeEnum fromDbValue(Integer dbValue) {
        return lookup.get(dbValue);
    }

    public Integer getDbValue() {
        return this.dbValue;
    }

}

Ahora puede cambiar el orden sin cambiar la búsqueda y viceversa.

jmkelm08
fuente
Esta es la respuesta correcta. Me sorprende que haya obtenido tan pocos puntos en comparación con otras respuestas más directas pero potencialmente inválidas (debido a cambios de código en el futuro).
Calabacin
8

Usted podría utilizar una tabla de consulta estática:

public enum Suit {
  spades, hearts, diamonds, clubs;

  private static final Map<Integer, Suit> lookup = new HashMap<Integer, Suit>();

  static{
    int ordinal = 0;
    for (Suit suit : EnumSet.allOf(Suit.class)) {
      lookup.put(ordinal, suit);
      ordinal+= 1;
    }
  }

  public Suit fromOrdinal(int ordinal) {
    return lookup.get(ordinal);
  }
}
ene
fuente
3
Ver también Enums .
trashgod
11
¡Guauu! ¡Simplemente guau! Esto es genial, por supuesto, pero ... ya sabes, el programador C dentro de mí grita de dolor al ver que asignas un HashMap completo y realizas búsquedas dentro de todo SÓLO para administrar esencialmente 4 constantes: espadas, corazones, diamantes y tréboles! El programador de AC asignaría 1 byte para cada: 'const char CLUBS = 0;' etc ... Sí, una búsqueda de HashMap es O (1), pero la memoria y la sobrecarga de la CPU de un HashMap, en este caso, hacen que muchos órdenes de magnitud sean más lentos y necesiten recursos que llamar a .values ​​() directamente. No es de extrañar que Java sea un gran recuerdo si la gente escribe así ...
Leszek
2
No todos los programas requieren el desempeño de un juego triple A. En muchos casos, el intercambio de memoria y CPU por seguridad de tipo, legibilidad, mantenibilidad, soporte multiplataforma, recolección de basura, etc. es justificable. Los idiomas de nivel superior existen por una razón.
Jan
3
Pero si su rango de claves es siempre 0...(n-1), entonces una matriz tiene menos código y también es más legible; El aumento de rendimiento es solo una ventaja. private static final Suit[] VALUES = values();y public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }. Ventaja adicional: se bloquea inmediatamente en ordinales no válidos, en lugar de devolver silenciosamente nulo. (No siempre es una ventaja. Pero a menudo.)
Thomas
4

Esto es lo que yo uso. No pretendo que sea mucho menos "eficiente" que las soluciones más simples anteriores. Lo que hace es proporcionar un mensaje de excepción mucho más claro que "ArrayIndexOutOfBounds" cuando se utiliza un valor ordinal no válido en la solución anterior.

Utiliza el hecho de que EnumSet javadoc especifica que el iterador devuelve elementos en su orden natural. Hay una afirmación si eso no es correcto.

La prueba JUnit4 demuestra cómo se usa.

 /**
 * convert ordinal to Enum
 * @param clzz may not be null
 * @param ordinal
 * @return e with e.ordinal( ) == ordinal
 * @throws IllegalArgumentException if ordinal out of range
 */
public static <E extends Enum<E> > E lookupEnum(Class<E> clzz, int ordinal) {
    EnumSet<E> set = EnumSet.allOf(clzz);
    if (ordinal < set.size()) {
        Iterator<E> iter = set.iterator();
        for (int i = 0; i < ordinal; i++) {
            iter.next();
        }
        E rval = iter.next();
        assert(rval.ordinal() == ordinal);
        return rval;
    }
    throw new IllegalArgumentException("Invalid value " + ordinal + " for " + clzz.getName( ) + ", must be < " + set.size());
}

@Test
public void lookupTest( ) {
    java.util.concurrent.TimeUnit tu = lookupEnum(TimeUnit.class, 3);
    System.out.println(tu);
}
gerardw
fuente
1

Esto es lo que hago en Android con Proguard:

public enum SomeStatus {
    UNINITIALIZED, STATUS_1, RESERVED_1, STATUS_2, RESERVED_2, STATUS_3;//do not change order

    private static SomeStatus[] values = null;
    public static SomeStatus fromInteger(int i) {
        if(SomeStatus.values == null) {
            SomeStatus.values = SomeStatus.values();
        }
        if (i < 0) return SomeStatus.values[0];
        if (i >= SomeStatus.values.length) return SomeStatus.values[0];
        return SomeStatus.values[i];
    }
}

es breve y no necesito preocuparme por tener una excepción en Proguard

Alguien en algún lugar
fuente
1

Puede definir un método simple como:

public enum Alphabet{
    A,B,C,D;

    public static Alphabet get(int index){
        return Alphabet.values()[index];
    }
}

Y úsalo como:

System.out.println(Alphabet.get(2));
Amir Fo
fuente
0
public enum Suit implements java.io.Serializable, Comparable<Suit>{
  spades, hearts, diamonds, clubs;
  private static final Suit [] lookup  = Suit.values();
  public Suit fromOrdinal(int ordinal) {
    if(ordinal< 1 || ordinal> 3) return null;
    return lookup[value-1];
  }
}

la clase de prueba

public class MainTest {
    public static void main(String[] args) {
        Suit d3 = Suit.diamonds;
        Suit d3Test = Suit.fromOrdinal(2);
        if(d3.equals(d3Test)){
            System.out.println("Susses");
        }else System.out.println("Fails");
    }
}

Le agradezco que comparta con nosotros si tiene un código más eficiente. Mi enumeración es enorme y se llama constantemente miles de veces.

Ar maj
fuente
Creo que quiso decir "if (ordinal <1 || ordinal> 4) return null;"
geowar
0

Entonces, una forma es hacer lo ExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];que funciona y es fácil, sin embargo, como se mencionó anteriormente, ExampleEnum.values()devuelve una nueva matriz clonada para cada llamada. Eso puede ser innecesariamente costoso. Podemos resolver eso almacenando en caché la matriz de esta manera ExampleEnum[] values = values(). También es "peligroso" permitir que se modifique nuestra matriz en caché. Alguien podría escribir, ExampleEnum.values[0] = ExampleEnum.type2;así que lo haría privado con un método de acceso que no haga copias adicionales.

private enum ExampleEnum{
    type0, type1, type2, type3;
    private static final ExampleEnum[] values = values();
    public static ExampleEnum value(int ord) {
        return values[ord];
    }
}

Utilizaría ExampleEnum.value(ordinal)para obtener el valor de enumeración asociado conordinal

joe pelletier
fuente
-1

Cada enumeración tiene nombre (), que proporciona una cadena con el nombre del miembro enumeración.

Dado enum Suit{Heart, Spade, Club, Diamond}, Suit.Heart.name()daráHeart .

Cada enumeración tiene un valueOf()método, que toma un tipo de enumeración y una cadena, para realizar la operación inversa:

Enum.valueOf(Suit.class, "Heart")vuelve Suit.Heart.

Por qué alguien usaría ordinales está más allá de mí. Puede ser nanosegundos más rápido, pero no es seguro, si los miembros de la enumeración cambian, ya que otro desarrollador puede no ser consciente de que algún código se basa en valores ordinales (especialmente en la página JSP citada en la pregunta, la sobrecarga de la red y la base de datos domina por completo el tiempo, no usar un entero sobre una cadena).

Tony BenBrahim
fuente
2
¿Porque comparar enteros es mucho más rápido que comparar cadenas?
HighCommander4
2
Pero los ordinales cambiarán si alguien modifica la enumeración (Agrega / reordena los miembros). A veces se trata de seguridad y no de velocidad, especialmente en una página JSP donde la latencia de la red es de 1,000,000 de veces la diferencia entre comparar una matriz de enteros (una cadena) y un solo entero.
Tony BenBrahim
toString puede anularse, por lo que es posible que no devuelva el nombre de enumeración. El nombre del método () es la que da el nombre de enumeración (es final)
gerardw
los comentarios de código y las versiones de aplicación también son una cosa (tal vez un formato de archivo es más simple y más pequeño de esta manera)
Joe Pelletier
No estoy seguro de por qué esto fue rechazado cuando se recomienda esencialmente lo mismo que en la respuesta de oxbow_lakes. Definitivamente más seguro que usar el ordinal.