Obtener enumeración asociada con valor int

89

Anteriormente, tenía mis enumeraciones LegNo definidas simplemente como:

NO_LEG, LEG_ONE, LEG_TWO

y al llamar return LegNo.values()[i];, pude obtener el valor asociado con cada enumeración.

Pero ahora he decidido que quiero que la LegNoenumeración NO_LEGsea ​​int -1 en lugar de 0, así que decidí usar un constructor privado para inicializar y establecer su valor int

NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

private LegNo(final int leg) { legNo = leg; }

lo único ahora es que como lo estoy haciendo de esta manera, el values()método no funcionará para la NO_LEGenumeración. ¿Cómo consigo la enumeración asociada con el int? ¿Hay alguna forma eficiente de hacer esto que no sea usar una instrucción de cambio de caso o un if-elseif-elseif

Puedo ver muchas preguntas SO relacionadas con la obtención del valor int de la enumeración, pero busco lo contrario.

L-Samuels
fuente

Respuestas:

148

EDITAR Agosto 2018

Hoy implementaría esto de la siguiente manera

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int value;

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

    public static Optional<LegNo> valueOf(int value) {
        return Arrays.stream(values())
            .filter(legNo -> legNo.value == value)
            .findFirst();
    }
}

Tendrá que mantener un mapeo dentro de la enumeración.

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private static Map<Integer, LegNo> map = new HashMap<Integer, LegNo>();

    static {
        for (LegNo legEnum : LegNo.values()) {
            map.put(legEnum.legNo, legEnum);
        }
    }

    private LegNo(final int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}

El bloque estático se invocará solo una vez, por lo que prácticamente no hay problemas de rendimiento aquí.

EDITAR: se cambió el nombre del método a valueOfque está más en línea con otras clases de Java.

Adarshr
fuente
Lo siento, no estoy seguro de haber sido lo suficientemente claro. quiero pasar el int y obtener la enumeración asociada con él.
L-Samuels
@ L-Samuels Supongo que no leí tu pregunta correctamente. Ver mi actualización.
adarshr
2
Sé que esto parece obvio, pero lo uso así: LegNo foo = LegNo.valueOf(2);. El código anterior devolverá un LegNo.LEG_TWO.
FirstOne
1
Debe tenerse en cuenta que si se pasa un valor entero no válido (no asignado), se devolverá null, como se esperaba mediante el uso de HashMap.get : Devuelve el valor al que se asigna la clave especificada, o nulo si este mapa no contiene ninguna asignación para la clave.
FirstOne
Si bien la sintaxis de la transmisión es ordenada, vale la pena señalar que tiene una mayor complejidad de tiempo que el mapa estático (que ciertamente tiene un mayor consumo de memoria). No es un problema para 3 valores, pero definitivamente una preocupación si está valueOf()usando una enumeración de 1000 miembros dentro de otro ciclo.
Patrick M
24

También podría incluir un método estático en la enumeración que recorra todos los miembros y devuelva el correcto.

public enum LegNo {
   NO_LEG(-1),
   LEG_ONE(1),
   LEG_TWO(2);

   private int legIndex;

   private LegNo(int legIndex) { this.legIndex = legIndex; }

   public static LegNo getLeg(int legIndex) {
      for (LegNo l : LegNo.values()) {
          if (l.legIndex == legIndex) return l;
      }
      throw new IllegalArgumentException("Leg not found. Amputated?");
   }
}

Ahora, si desea obtener un valor de Enum por entero, simplemente use:

int myLegIndex = 1; //expected : LEG_ONE
LegNo myLeg = LegNo.getLeg(myLegIndex);
Mike Adler
fuente
Supongo que esto sería más elegante que usar una declaración if else if. Pero dado que había más enumeraciones para buscar, la estrategia de mapa sugerida por @adarshr sería mejor. Aunque vote por el humor.
L-Samuels
1
También me gusta mucho la estrategia del mapa. Especialmente cuando la enumeración tiene muchos valores o se debe buscar a través de este mecanismo con mucha frecuencia. Sin embargo, si buscar valores por el int asociado es una ocurrencia relativamente rara o si tiene muchas enumeraciones diferentes con el mismo requisito de búsqueda, creo que mi método sería más amigable con los recursos, ya que la sobrecarga del mapa se guarda. Además, creo que hace que el código sea menos desordenado. Sin embargo, tengo algunos casos de uso en los que definitivamente cambiaré al tipo de mapa.
Mike Adler
Nunca debe derivar un valor de enumeración asociado por su ordinal. El uso de un mapa estático ES la metodología recomendada por los arquitectos de Java.
hfontanez
El campo legIndex coincide con el ordinal en este ejemplo, pero puede ser cualquier valor int. No se realiza ninguna búsqueda ordinal. Además, proporcione o vincule una razón por la que cree que la búsqueda ordinal es mala.
Mike Adler
1
"Pierna no encontrada. ¿Amputado?"
Gnagy
17

La respuesta de adarshr adaptada a Java 8:

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap;

import java.util.Map;

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    private final static Map<Integer, LegNo> map =
            stream(LegNo.values()).collect(toMap(leg -> leg.legNo, leg -> leg));

    private LegNo(final int leg) {
        legNo = leg;
    }

    public static LegNo valueOf(int legNo) {
        return map.get(legNo);
    }
}
Marcin
fuente
11

También puede acceder al valor Enum correspondiente a un valor entero dado simplemente llamando al método values ​​() en Enum LegNo. Devuelve el campo de las enumeraciones LegNo: LegNo.values()[0]; //returns LEG_NO LegNo.values()[1]; //returns LEG_ONE LegNo.values()[2]; //returns LEG_TWO

No era precisamente lo que estaba buscando, pero bastante cercano y muy simple de hecho. (Aunque el sujeto está muerto, podría ser útil para otra persona).

Tadeas
fuente
6

Java 8 way con valor predeterminado:

public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private final int legNo;

    LegNo(int legNo) {
        this.legNo = legNo;
    }

    public static LegNo find(int legNo, Supplier<? extends LegNo> byDef) {
        return Arrays.asList(LegNo.values()).stream()
                .filter(e -> e.legNo == legNo).findFirst().orElseGet(byDef);
    }
}

llamar:

LegNo res = LegNo.find(0, () -> LegNo.NO_LEG);

o con excepción:

LegNo res = LegNo.find(0, () -> {
    throw new RuntimeException("No found");
});
Dmitry Sokolyuk
fuente
2
public enum LegNo {

  NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

  private final int code;

  LegNo(int code) {
    this.code = code;
    ReverseStorage.reverseMap.put(code, this);
  }

  public static Optional<LegNo> getByCode(int code) {
    return Optional.ofNullable(ReverseStorage.reverseMap.get(code));
  }

  private static final class ReverseStorage {
    private static final Map<Integer, LegNo> reverseMap = new LinkedHashMap<>();
  }
}
Andrey Lebedenko
fuente
1

Dado que su enumeración solo contiene 3 elementos, la forma más rápida será usar una serie de if else, como sugirió.

editar: la respuesta que proporcionó adarshr es más adecuada para casos generales, donde hay muchos valores de enumeración, pero creo que es una exageración para su problema.

DieterDP
fuente
Tener un Mapcódigo en su código ciertamente no es una exageración. Además, hace que el método sea mucho más limpio que un espagueti de condiciones if-else.
adarshr
Estoy de acuerdo en que el mapa es mejor una vez que tienes muchos valores de enumeración, pero para 3 valores me apegaría a una construcción if / else. Supongo que es cuestión de gustos.
DieterDP
Cualquiera que sea el enfoque que elijamos, la firma del método public LegNo valueOf(int value)no debe cambiarse. El if-else podría escribirse dentro de la enumeración misma. Si el if-else sale de la enumeración, entonces ciertamente se convierte en un código no tan limpio.
adarshr
1
Estoy completamente de acuerdo contigo :)
DieterDP
1
public enum LegNo {
    NO_LEG(-1), LEG_ONE(1), LEG_TWO(2);

    private int legNo;

    private LegNo(int leg) { legNo = leg; }

    public static LegNo valueOf(int legNo) {
        for (LegNo leg : LegNo.values()) {
            if (leg.legNo == legNo) return leg;
        }   
    }
}

assert LegNo.valueOf(2) == LegNo.LEG_TWO
assert LegNo.valueOf(3) == null
Tumba
fuente
4
Aceptable para enumeraciones con <10 valores pero totalmente ineficaz para un gran número de valores de enumeración debido a la complejidad de búsqueda O (n)
Alfishe