Converter valor inteiro para correspondência Java Enum

86

Eu tenho um enum como este:

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);
    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

Agora eu obtenho um int de uma entrada externa e quero a entrada correspondente - lançar uma exceção se um valor não existir está ok, mas de preferência, seria DLT_UNKNOWN nesse caso.

int val = in.readInt();
PcapLinkType type = ???; /*convert val to a PcapLinkType */
Lyke
fonte

Respostas:

105

Você precisaria fazer isso manualmente, adicionando um mapa estático na classe que mapeia inteiros para enums, como

private static final Map<Integer, PcapLinkType> intToTypeMap = new HashMap<Integer, PcapLinkType>();
static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.value, type);
    }
}

public static PcapLinkType fromInt(int i) {
    PcapLinkType type = intToTypeMap.get(Integer.valueOf(i));
    if (type == null) 
        return PcapLinkType.DLT_UNKNOWN;
    return type;
}
MeBigFatGuy
fonte
1
atualizado com recomendações da dty, o que foi uma boa ideia.
MeBigFatGuy
Espero que primeiro você tenha executado meu código por meio de um compilador ... Acabei de inventá-lo de início. Eu sei que a técnica funciona - usei-a ontem. Mas o código está em outra máquina e esta não tem minhas ferramentas de desenvolvimento.
dty
1
allOf está disponível apenas para conjuntos
MeBigFatGuy
1
Além disso, EnumMapusa os enums como chaves. Nesse caso, o OP deseja os enums como os valores.
dty
8
Isso parece muita sobrecarga desnecessária. Aqueles que realmente precisam desse tipo de operação provavelmente precisam de alto desempenho porque estão gravando / lendo de streams / sockets, nesse caso, o cache de values()(se seus valores enum forem sequenciais) ou uma switchinstrução simples venceria este método facilmente . Se você tiver apenas um punhado de entradas em seu Enum, não faz muito sentido adicionar a sobrecarga de um HashMap simplesmente para a conveniência de não ter que atualizar a switchinstrução. Esse método pode parecer mais elegante, mas também é um desperdício.
esmagamento de
30

Existe um método estático values()que está documentado, mas não onde você esperava: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

enum MyEnum {
    FIRST, SECOND, THIRD;
    private static MyEnum[] allValues = values();
    public static MyEnum fromOrdinal(int n) {return allValues[n];}
}

Em princípio, você pode usar just values()[i], mas há rumores de que values()criarão uma cópia do array cada vez que for invocado.

18446744073709551615
fonte
9
De acordo com Joshua Bloch (Livro Java Efetivo) : Nunca derivar um valor associado a um enum de seu ordinal; Sua implementação não deve depender da ordem enums.
stevo.mit
4
Implementação de quê? Se implementarmos algum algoritmo, a implementação não deve depender da ordem de enums, a menos que essa ordem seja documentada. Quando implementamos o próprio enum, não há problema em usar tais detalhes de implementação, da mesma forma que usar métodos privados de classe.
18446744073709551615
1
Não concordo. Eu acredito que nunca se destina a documentação independente. Você não deve usar ordinais, mesmo quando implementar enum por conta própria. É um cheiro ruim e está sujeito a erros. Não sou um especialista, mas não discutiria com Joshua Bloch :)
stevo.mit
4
@ stevo.mit dê uma olhada no novo enum java.time.Month em Java 8. O método estático Month.of (int) faz exatamente o que Joshua Bloch disse que você "nunca" deveria fazer. Ele retorna um mês com base em seu ordinal.
Klitos Kyriacou
1
@ stevo.mit Existem enums ordenados e enums não ordenados . (E enums de bitmask também.) É simplesmente incorreto falar deles apenas como "enums". A decisão de quais meios expressivos usar deve ser baseada no nível de abstração em que você trabalha. Na verdade, é incorreto usar detalhes de implementação (meios expressivos do nível inferior) ou suposições de uso (meios expressivos do nível superior). Quanto a " nunca ", nas línguas humanas nunca nunca significa nunca, porque sempre existe algum contexto. (Normalmente, na programação de aplicativos, nunca ...) BTW, programering.com/a/MzNxQjMwATM.html
18446744073709551615
14

Você terá que criar um novo método estático em que itera PcapLinkType.values ​​() e compare:

public static PcapLinkType forCode(int code) {
    for (PcapLinkType typе : PcapLinkType.values()) {
        if (type.getValue() == code) {
            return type;
        }
    }
    return null;
 }

Isso seria ótimo se raramente fosse chamado. Se for chamado com frequência, observe a Mapotimização sugerida por outros.

Bozho
fonte
4
Pode ser caro se for muito chamado. A construção de um mapa estático provavelmente proporcionará um custo amortizado melhor.
dty
@dty o (n) com n = 200 - não acho que seja um problema
Bozho
7
Essa é uma declaração totalmente ridícula, sem uma noção da frequência com que é chamada. Se for chamado uma vez, ótimo. Se for chamado para cada pacote que passa zunindo em uma rede 10Ge, tornar um algoritmo 200x mais rápido é muito importante. Daí porque qualifiquei minha declaração com "se muito for chamado"
dty
10

Você pode fazer algo assim para registrá-los automaticamente em uma coleção com a qual converter facilmente os inteiros para o enum correspondente. (Aliás, adicioná-los ao mapa no construtor enum não é permitido . É bom aprender coisas novas mesmo depois de muitos anos usando Java. :)

public enum PcapLinkType {
    DLT_NULL(0),
    DLT_EN10MB(1),
    DLT_EN3MB(2),
    DLT_AX25(3),
    /*snip, 200 more enums, not always consecutive.*/
    DLT_UNKNOWN(-1);

    private static final Map<Integer, PcapLinkType> typesByValue = new HashMap<Integer, PcapLinkType>();

    static {
        for (PcapLinkType type : PcapLinkType.values()) {
            typesByValue.put(type.value, type);
        }
    }

    private final int value;

    private PcapLinkType(int value) {
        this.value = value;
    }

    public static PcapLinkType forValue(int value) {
        return typesByValue.get(value);
    }
}
Esko Luontola
fonte
1
Isso é o que você ganha por verificar sua resposta antes de postar. ;)
Esko Luontola
10

se você tem enum como este

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  DLT_UNKNOWN(-1);

    private final int value;   

    PcapLinkType(int value) {
        this.value= value;
    }
}

então você pode usá-lo como

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Jack Gajanan
fonte
você perdeu o comentário / * recorte, mais 200 enums, nem sempre consecutivos. * /
MeBigFatGuy
apenas no caso de seu valor enum ser transitividade de Zero, isso é uma prática ruim
cuasodayleo
4

Como @MeBigFatGuy diz, exceto que você pode fazer seu static {...}bloco usar um loop sobre a values()coleção:

static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.getValue(), type);
    }
}
dty
fonte
4

Sei que essa pergunta já existe há alguns anos, mas como o Java 8, entretanto, nos trouxe Optional, pensei em oferecer uma solução usando ele (e Streame Collectors):

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  // DLT_UNKNOWN(-1); // <--- NO LONGER NEEDED

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static Optional<PcapLinkType> fromInt(int value) {
    return Optional.ofNullable(map.get(value));
  }
}

Optionalé como null: representa um caso em que não há valor (válido). Mas é uma alternativa mais segura para o tipo nullou um valor padrão, como DLT_UNKNOWNporque você pode esquecer de verificar os casos nullou DLT_UNKNOWN. Ambos são PcapLinkTypevalores válidos ! Em contraste, você não pode atribuir um Optional<PcapLinkType>valor a uma variável do tipo PcapLinkType. Optionalfaz com que você verifique um valor válido primeiro.

Claro, se você quiser manter DLT_UNKNOWNpara compatibilidade com versões anteriores ou qualquer outro motivo, você ainda pode usar, Optionalmesmo nesse caso, usando orElse()para especificá-lo como o valor padrão:

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);

  private final int value;
  private PcapLinkType(int value) { this.value = value; }

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static PcapLinkType fromInt(int value) {
    return Optional.ofNullable(map.get(value)).orElse(DLT_UNKNOWN);
  }
}
Brad Collins
fonte
3

Você pode adicionar um método estático em seu enum que aceite um intcomo um parâmetro e retorne um PcapLinkType.

public static PcapLinkType of(int linkType) {

    switch (linkType) {
        case -1: return DLT_UNKNOWN
        case 0: return DLT_NULL;

        //ETC....

        default: return null;

    }
}
Buhake Sindi
fonte
Melhor não se esquecer de adicionar uma entrada a essa switchinstrução se você adicionar um novo enum. Não é o ideal, IMHO.
dty
1
@dty Então, você acha que a sobrecarga de um HashMap supera a necessidade de adicionar um novo caso a uma instrução switch?
esmagamento de
1
Acho que prefiro escrever um código que me ajude a não cometer erros e, portanto, é mais provável que esteja correto antes de me concentrar no micro-desempenho de uma pesquisa de hash.
dty
3

Isso é o que eu uso:

public enum Quality {ENOUGH,BETTER,BEST;
                     private static final int amount = EnumSet.allOf(Quality.class).size();
                     private static Quality[] val = new Quality[amount];
                     static{ for(Quality q:EnumSet.allOf(Quality.class)){ val[q.ordinal()]=q; } }
                     public static Quality fromInt(int i) { return val[i]; }
                     public Quality next() { return fromInt((ordinal()+1)%amount); }
                    }
18446744073709551615
fonte
O uso de ordinal foi identificado como uma prática inadequada, em geral, é melhor evitar.
Rafael
1
static final PcapLinkType[] values  = { DLT_NULL, DLT_EN10MB, DLT_EN3MB, null ...}    

...

public static PcapLinkType  getPcapLinkTypeForInt(int num){    
    try{    
       return values[int];    
    }catch(ArrayIndexOutOfBoundsException e){    
       return DLT_UKNOWN;    
    }    
}    
nsfyn55
fonte
1
Caro se for muito chamado. Precisa se lembrar de atualizar o array (por que você o tem quando enums definem um .values()método?).
dty
@dty é o try / catch? Acho que seria mais justo dizer que é caro se muitos dos valores se enquadrarem na categoria DLT_UNKNOWN.
nsfyn55
1
Estou realmente surpreso ao ver uma solução de array rejeitada e uma solução de mapa rejeitada. O que eu não gosto aqui é --int, mas é obviamente um erro de digitação.
18446744073709551615
Entendo: eles querem nullno lugar de DLT_UKNOWN:)
18446744073709551615
1
Porque não static final values[] = PcapLinkType.values()?
18446744073709551615
0

Não há como manipular elegantemente tipos enumerados baseados em inteiros. Você pode pensar em usar uma enumeração baseada em string em vez de sua solução. Não é uma forma preferida todas as vezes, mas ainda existe.

public enum Port {
  /**
   * The default port for the push server.
   */
  DEFAULT("443"),

  /**
   * The alternative port that can be used to bypass firewall checks
   * made to the default <i>HTTPS</i> port.
   */
  ALTERNATIVE("2197");

  private final String portString;

  Port(final String portString) {
    this.portString = portString;
  }

  /**
   * Returns the port for given {@link Port} enumeration value.
   * @return The port of the push server host.
   */
  public Integer toInteger() {
    return Integer.parseInt(portString);
  }
}
Buğra Ekuklu
fonte