Convierta un valor entero en Java Enum coincidente

86

Tengo una enumeración como esta:

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

Ahora obtengo un int de la entrada externa y quiero la entrada coincidente: lanzar una excepción si un valor no existe está bien, pero preferiblemente lo haría DLT_UNKNOWN en ese caso.

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

Respuestas:

105

Debería hacer esto manualmente, agregando un mapa estático en la clase que asigna Integers a enumeraciones, 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
fuente
1
actualizado con recomendaciones de dty, lo cual fue una buena idea.
MeBigFatGuy
Espero que primero hayas ejecutado mi código a través de un compilador ... Me lo acabo de inventar. Sé que la técnica funciona, la usé ayer. Pero el código está en otra máquina y esta no tiene mis herramientas de desarrollo.
dty
1
allOf solo está disponible para conjuntos
MeBigFatGuy
1
Además, EnumMapusa las enumeraciones como claves. En este caso, el OP quiere las enumeraciones como valores.
Dty
8
Esto parece una gran cantidad de gastos generales innecesarios. Aquellos que realmente necesitan este tipo de operación probablemente necesiten un alto rendimiento porque están escribiendo / leyendo desde flujos / sockets, en cuyo caso, el almacenamiento en caché de values()(si sus valores de enumeración son secuenciales) o una switchdeclaración simple superaría este método fácilmente . Si solo tiene un puñado de entradas en su, Enumentonces no tiene mucho sentido agregar la sobrecarga de un HashMap simplemente por la conveniencia de no tener que actualizar la switchdeclaración. Este método puede parecer más elegante, pero también es un desperdicio.
aplastar
30

Hay un método estático values()que está documentado, pero no donde lo esperaría: 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];}
}

En principio, puede usar solo values()[i], pero hay rumores que values()crearán una copia de la matriz cada vez que se invoque.

18446744073709551615
fuente
9
Según Joshua Bloch (Libro efectivo de Java) : Nunca derive un valor asociado con una enumeración de su ordinal; Su implementación no debería depender del orden de enumeraciones.
stevo.mit
4
¿Implementación de qué? Si implementamos algún algoritmo, la implementación no debería depender del orden de enumeraciones menos que ese orden esté documentado. Cuando implementamos la enumeración en sí, está bien usar dichos detalles de implementación, de la misma manera que está bien usar métodos privados de clase.
18446744073709551615
1
No estoy de acuerdo. Creo que nunca se entiende independientemente de la documentación. No debe usar ordinales incluso cuando implemente enum usted mismo. Tiene mal olor y es propenso a errores. No soy un experto, pero no discutiría con Joshua Bloch :)
stevo.mit
4
@ stevo.mit echa un vistazo a la nueva enumeración java.time.Month en Java 8. El método estático Month.of (int) hace exactamente lo que Joshua Bloch dijo que "nunca" debes hacer. Devuelve un mes según su ordinal.
Klitos Kyriacou
1
@ stevo.mit Hay enumeraciones ordenadas y enumeraciones desordenadas . (Y también enumeraciones de máscara de bits ). Es simplemente incorrecto hablar de ellos simplemente como "enumeraciones". La decisión de qué medio expresivo utilizar debe basarse en el nivel de abstracción en el que trabaje. De hecho, es incorrecto utilizar detalles de implementación (medios expresivos del nivel inferior) o supuestos de uso (medios expresivos del nivel superior). En cuanto a " nunca ", en los lenguajes humanos nunca nunca significa nunca, porque siempre hay algún contexto. (Por lo general, en la programación de aplicaciones, nunca ...) Por cierto, programering.com/a/MzNxQjMwATM.html
18446744073709551615
14

Tendrás que crear un nuevo método estático en el que iterarás PcapLinkType.values ​​() y compararás:

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

Eso estaría bien si se llama raramente. Si se llama con frecuencia, observe la Mapoptimización sugerida por otros.

Bozho
fuente
4
Puede ser caro si se llama mucho. Es probable que la construcción de un mapa estático proporcione un mejor costo amortizado.
dty
@dty o (n) con n = 200 - no creo que sea un problema
Bozho
7
Esa es una declaración totalmente ridícula sin una idea de la frecuencia con la que se llama. Si se llama una vez, está bien. Si se solicita por cada paquete que pasa zumbando en una red de 10Ge, es muy importante hacer un algoritmo 200 veces más rápido. Por lo tanto, califiqué mi declaración con "si se llama mucho"
dty
10

Puede hacer algo como esto para registrarlos todos automáticamente en una colección con la que luego convertir fácilmente los enteros en la enumeración correspondiente. (Por cierto, no se permite agregarlos al mapa en el constructor de enumeración . Es bueno aprender cosas nuevas incluso después de muchos años de usar 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
fuente
1
Eso es lo que obtienes por verificar tu respuesta antes de publicarla. ;)
Esko Luontola
10

si tienes una enumeración como esta

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

entonces puedes usarlo como

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Jack Gajanan
fuente
te perdiste el comentario / * recorte, 200 enumeraciones más, no siempre consecutivas. * /
MeBigFatGuy
Sólo en caso de que su valor de enumeración es transitividad de cero, esto es una mala práctica
cuasodayleo
4

Como dice @MeBigFatGuy, excepto que puede hacer que su static {...}bloque use un bucle sobre la values()colección:

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

Sé que esta pregunta tiene algunos años, pero como Java 8, mientras tanto, nos trajo Optional, pensé en ofrecer una solución usándola (y Streamy 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));
  }
}

Optionales como null: representa un caso en el que no hay ningún valor (válido). Pero es una alternativa más segura para los tipos nullo un valor predeterminado, por ejemplo, DLT_UNKNOWNporque podría olvidarse de verificar los casos nullo DLT_UNKNOWN. ¡Ambos son PcapLinkTypevalores válidos ! Por el contrario, no puede asignar un Optional<PcapLinkType>valor a una variable de tipo PcapLinkType.Optionalle hace comprobar primero un valor válido.

Por supuesto, si desea conservar DLT_UNKNOWNpara compatibilidad con versiones anteriores o cualquier otra razón, aún puede usar Optionalincluso en ese caso, usando orElse()para especificarlo como el valor predeterminado:

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
fuente
3

Puede agregar un método estático en su enumeración que acepte un intcomo parámetro y devuelva un 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
fuente
Mejor no olvide agregar una entrada a esa switchdeclaración si agrega una nueva enumeración. No es ideal, en mi humilde opinión.
dty
1
@dty Entonces, ¿cree que la sobrecarga de un HashMap supera la necesidad de agregar un nuevo caso a una declaración de cambio?
aplastar
1
Creo que prefiero escribir un código que me ayude a no cometer errores y, por lo tanto, es más probable que sea correcto antes de centrarme en el micro rendimiento de una búsqueda de hash.
dty
3

Esto es lo que 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
fuente
El uso de ordinal se ha identificado como una mala práctica, en general, es mejor evitarlo.
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
fuente
1
Caro si se llama mucho. Necesito recordar actualizar la matriz (¿por qué la tiene cuando las enumeraciones definen un .values()método?).
dty
@dty ¿es el intento / captura? Creo que sería más justo decir que es caro si muchos de los valores caen en la categoría DLT_UNKNOWN.
nsfyn55
1
Estoy realmente sorprendido de ver una solución de matriz rechazada y una solución de mapa votada a favor. Lo que no me gusta aquí es --int, pero obviamente es un error tipográfico.
18446744073709551615
Ya veo: quieren nullen lugar de DLT_UKNOWN:)
18446744073709551615
1
¿Por qué no static final values[] = PcapLinkType.values()?
18446744073709551615
0

No hay forma de manejar con elegancia los tipos enumerados basados ​​en números enteros. Podría pensar en usar una enumeración basada en cadenas en lugar de su solución. No es una forma preferida todas las veces, pero aún 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
fuente