Converter do tipo enum ordinal para enum

316

Eu tenho o tipo de enum ReportTypeEnumque é passado entre métodos em todas as minhas classes, mas preciso passar isso na URL para usar o método ordinal para obter o valor int. Depois de obtê-lo em minha outra página JSP, preciso convertê-lo em volta para um ReportTypeEnumpara que eu possa continuar passando.

Como posso converter ordinal para o ReportTypeEnum?

Usando o Java 6 SE.

Lennie
fonte
1
Não há Java 6 EE, até agora (AFAIK). Há Java SE 6 e Java EE 5.
Hosam Aly

Respostas:

632

Para converter um ordinal em sua representação enum, convém fazer o seguinte:

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

Observe os limites da matriz.

Observe que toda chamada para values()retorna um array clonado recentemente que pode afetar o desempenho de maneira negativa. Você pode querer armazenar em cache a matriz se ela for chamada com frequência.

Exemplo de código sobre como fazer cachevalues() .


Esta resposta foi editada para incluir o feedback fornecido dentro dos comentários

Joachim Sauer
fonte
Eu implementei esta solução e ela não está funcionando para mim. Ele retorna que o valor ordinal não é garantido para corresponder à ordem em que os tipos enumerados são adicionados. Eu não sei o que é o que esta resposta está defendendo, mas eu queria para avisar as pessoas, no entanto
IcedDante
@IcesDante: certamente o ordinal corresponde à ordem dos valores de Enumeração na fonte. Se você observar um comportamento diferente, algo mais deve estar errado. Minha resposta acima é, no entanto, subótima por todas as razões expostas nas outras respostas.
Joachim Sauer
@JoachimSauer Talvez IcedDante significa que o ordinal pode não corresponder se tiver sido produzido e salvo por uma versão anterior da fonte, na qual os valores da enumeração estão em uma ordem diferente.
LarsH
137

Esta é quase certamente uma má ideia . Certamente, se o ordinal é de fato persistido (por exemplo, porque alguém marcou o URL como favorito) - isso significa que você deve sempre preservar a enumordem no futuro, o que pode não ser óbvio para os mantenedores de código na linha.

Por que não codificar o enumuso myEnumValue.name()(e decodificar via ReportTypeEnum.valueOf(s))?

oxbow_lakes
fonte
24
E se você alterar o nome da enumeração (mas manter a ordem)?
Arne Evertsson 11/11/2009
6
@ Arne - Eu acho que isso é muito menos provável do que uma pessoa inexperiente aparecendo e adicionando um valueno início ou em sua posição alfabética / lógica correta. (Por lógica Quer dizer, por exemplo, TimeUnitos valores têm uma posição lógica)
oxbow_lakes
7
Eu certamente prefiro forçar a ordem das enumerações do que o nome da minha enumeração ... é por isso que prefiro armazenar o ordinal do que o nome da enumeração no banco de dados. Além disso, é melhor usar a manipulação int em vez de cordas ...
8
Concordo. Em uma API pública, alterar o nome de um Enum interromperia a compatibilidade com versões anteriores, mas alterar a ordem. Por essa razão, faz mais sentido usar o nome como sua "chave"
Noel
3
O armazenamento do ordinal facilita a tradução de suas idéias para outros idiomas. E se você precisar escrever algum componente em C?
QED
94

Se eu vou usar values()muito:

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

Enquanto isso, where.java:

Suit suit = Suit.values[ordinal];

Mente seus limites de matriz.

QED
fonte
3
+1 é, de longe, a melhor solução IMHO, porque é possível passar ordinais, especialmente no android.os.Message.
likejudo
9
Isso é muito preocupante porque matrizes são mutáveis . Mesmo que o valor [] seja final, ele não impede Suit.values[0] = Suit.Diamonds;em algum lugar do seu código. Idealmente, isso nunca acontecerá, mas o princípio geral de não expor campos mutáveis ainda é válido. Para essa abordagem, considere usar Collections.unmodifiableListou algo semelhante.
Mshnik
Que tal - terno estático privado final values ​​[] = values ​​(); public static Suit [] getValues ​​() {retorna valores; }
Pratyush
4
@Pratyush que tornaria a variável da matriz imutável, mas não o seu conteúdo. Eu ainda poderia fazer getValues ​​() [0] = somethingElse;
Calabacin
Concordo, portanto, o ponto não é expor o Suit values[]direta ou indiretamente (como foi mencionado getValues()), eu trabalho em torno de um método público onde o ordinalvalor deve ser enviado como um argumento e retornar a Suitrepresentação deSuit values[] . O ponto aqui (telhas da questão desde o início) era criar um tipo de enumeração de um ordinal enum
Manuel Jordan
13

Concordo com a maioria das pessoas que usar ordinal é provavelmente uma má ideia. Normalmente, resolvo esse problema fornecendo ao enum um construtor privado que pode usar, por exemplo, um valor de banco de dados e criar uma fromDbValuefunção estática semelhante à da resposta 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;
    }

}

Agora você pode alterar a ordem sem alterar a pesquisa e vice-versa.

jmkelm08
fonte
Esta é a resposta certa. Estou surpreso que tenha conseguido tão poucos pontos em comparação com outras respostas mais diretas, mas potencialmente inválidas (devido a alterações de código no futuro).
Calabacin
8

Você pode usar uma tabela de pesquisa 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);
  }
}
Jan
fonte
3
Veja também Enums .
trashgod
11
Uau! Apenas Uau! Isso é legal, é claro, mas ... você sabe - o programador C dentro de mim grita de dor ao ver que você aloca um HashMap completo e executa pesquisas dentro de tudo isso APENAS para gerenciar essencialmente 4 constantes: espadas, corações, diamantes e clubes! O programador de CA alocaria 1 byte para cada: 'const char CLUBS = 0;' etc ... Sim, uma pesquisa no HashMap é O (1), mas a sobrecarga de memória e CPU de um HashMap, nesse caso, torna muitas ordens de magnitude mais lentas e sem recursos do que chamar diretamente .values ​​()! Não admira Java é tal um devorador de memória, se as pessoas escrevem assim ...
Leszek
2
Nem todo programa requer o desempenho de um jogo A triplo. Em muitos casos, a troca de memória e CPU por segurança de tipo, legibilidade, manutenção, suporte de plataforma cruzada, coleta de lixo, etc ... é justificável. Existem idiomas de nível superior por um motivo.
Jan
3
Mas se seu intervalo de chaves for sempre 0...(n-1), uma matriz será menos código e mais legível também; o aumento de desempenho é apenas um bônus. private static final Suit[] VALUES = values();e public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }. Vantagem extra: falha imediatamente em ordinais inválidos, em vez de retornar silenciosamente nulo. (Nem sempre uma vantagem, mas muitas vezes..)
Thomas
4

É isso que eu uso. Não pretendo que seja muito menos "eficiente" do que as soluções mais simples acima. O que ele faz é fornecer uma mensagem de exceção muito mais clara que "ArrayIndexOutOfBounds" quando um valor ordinal inválido é usado na solução acima.

Ele utiliza o fato de que o javadoc EnumSet especifica o iterador retorna os elementos em sua ordem natural. Há uma afirmação se isso não estiver correto.

O teste JUnit4 demonstra como é usado.

 /**
 * 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
fonte
1

Isto é o que eu faço no Android com 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];
    }
}

é curto e não preciso me preocupar em ter uma exceção no Proguard

Alguém algum lugar
fonte
1

Você pode definir um método simples como:

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

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

E use-o como:

System.out.println(Alphabet.get(2));
Amir Fo
fonte
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];
  }
}

a classe de teste

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

Compreendo que você compartilhe conosco se tiver um código mais eficiente. Meu enum é enorme e é constantemente chamado milhares de vezes.

Ar maj
fonte
Eu acho que você quis dizer "se (ordinal <1 || ordinal> 4) retorne nulo;"
geowar 16/04
0

Portanto, uma maneira de fazer o ExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];que funciona e é fácil, no entanto, como mencionado anteriormente, ExampleEnum.values()retorna um novo array clonado para cada chamada. Isso pode ser desnecessariamente caro. Podemos resolver isso armazenando em cache a matriz dessa maneira ExampleEnum[] values = values(). Também é "perigoso" permitir que nossa matriz em cache seja modificada. Alguém poderia escrever ExampleEnum.values[0] = ExampleEnum.type2;Então, eu o tornaria privado com um método acessador que não faça cópias extras.

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

Você usaria ExampleEnum.value(ordinal)para obter o valor de enum associado aordinal

joe pelletier
fonte
-1

Cada enumeração tem nome (), que fornece uma sequência com o nome do membro da enumeração.

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

Toda enumeração tem um valueOf() método, que usa um tipo de enum e uma string, para executar a operação reversa:

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

Por que alguém usaria ordinais está além de mim. Pode ser em nanossegundos mais rápido, mas não é seguro, se os membros da enum mudarem, pois outro desenvolvedor pode não estar ciente de que algum código depende de valores ordinais (especialmente na página JSP citada na pergunta, a sobrecarga da rede e do banco de dados domina completamente o time, sem usar um número inteiro sobre uma string).

Tony BenBrahim
fonte
2
Porque comparar números inteiros é muito mais rápido que comparar seqüências de caracteres?
precisa saber é o seguinte
2
Porém, os ordinais serão alterados se alguém modificar a enumeração (Adicionar / reordenar memvers). Às vezes, trata-se de segurança e não de velocidade, especialmente em uma página JSP em que a latência da rede é 1.000.000 vezes a diferença entre comparar uma matriz de números inteiros (uma string) e um único inteiro.
Tony Benbrahim
toString pode ser substituído, portanto, pode não retornar o nome da enumeração. O nome do método () é o que dá o nome enum (é final)
gerardw
comentários de código e versões de aplicativos também são uma coisa (talvez um formato de arquivo é mais simples e menor deste modo)
joe Pelletier
Não sei por que isso foi prejudicado quando se recomenda essencialmente a mesma coisa que na resposta de oxbow_lakes. Definitivamente mais seguro do que usar o ordinal.