Cast Int per enum in Java


335

Qual è il modo corretto di lanciare un Int in un enum in Java dato il seguente enum?

public enum MyEnum
{
    EnumValue1,
    EnumValue2
}


MyEnum enumValue = (MyEnum) x; //Doesn't work???

@RyuS. Questo non è un duplicato di questa domanda,
Mark Rotteveel,

Risposte:


588

Prova MyEnum.values()[x]dove xdeve essere 0o 1, cioè un ordinale valido per quell'enum.

Si noti che in Java gli enum in realtà sono classi (e quindi i valori enum sono oggetti) e quindi non è possibile eseguire il cast di un enum into addirittura Integerdi un enum.


117
+1: potresti voler memorizzare nella cache MyEnum.values()come costoso. cioè se lo chiami centinaia di volte.
Peter Lawrey,

6
@PeterLawrey Solo per completezza, puoi spiegare perché dovrebbe essere lento? Non vedo alcuna ragione ovvia per questo.
Tarrasch,

48
@Tarrasch poiché le matrici sono mutabili, i valori () devono restituire una copia della matrice di elementi nel caso in cui vi capiti di cambiarla. La creazione di questa copia ogni volta è relativamente costosa.
Peter Lawrey,

9
@PeterLawrey Ultimamente ho usato molto haskell (dove tutto è immutabile)! Grazie per la tua spiegazione chiara e concisa. :)
Tarrasch il

come ottenere la 'x'?
Beeing Jk,

161

MyEnum.values()[x]è un'operazione costosa. Se la performance è un problema, potresti voler fare qualcosa del genere:

public enum MyEnum {
    EnumValue1,
    EnumValue2;

    public static MyEnum fromInteger(int x) {
        switch(x) {
        case 0:
            return EnumValue1;
        case 1:
            return EnumValue2;
        }
        return null;
    }
}

44
Se si desidera evitare la manutenzione dello switch, quindi sulla classe using: private final MyEnum [] myEnumValues ​​= MyEnum.values ​​(); Quindi utilizzo: myEnum = myEnumValues ​​[i];
Gili Nachum,

23
@GiliNachum lo ha detto in modo strano, ma il problema con questa soluzione è la manutenibilità. Va contro il principio DRY, che significa ogni volta che i valori enum vengono modificati (riordinati, valori aggiunti, valori rimossi) l'istruzione switch deve essere aggiornata contemporaneamente. Il commento di Gili obbliga Java a mantenere la coerenza con l'elenco dei valori enum, le modifiche ai valori enum non influiscono affatto su questo approccio.
Dandre Allison,

3
@LorenzoPolidori, Puoi spiegare perché consideri MyEnum.values()[x]un'operazione costosa. Non so come funzioni nei dettagli, ma per me sembra che accedere a un elemento in un array non sarebbe un grosso problema, alias tempo costante. Se l'array deve essere compilato, ci vuole O (n) tempo, che è lo stesso tempo di esecuzione della soluzione.
Brunsgaard

1
@brunsgaard Presumo values()generi ogni volta un nuovo array, perché gli array sono mutabili, quindi non sarebbe sicuro restituire lo stesso più volte. Le istruzioni switch non sono necessariamente O (n), possono essere compilate per saltare le tabelle. Quindi le affermazioni di Lorenzo sembrano giustificate.
MikeFHay,

1
La versione di @Johnson Gili non richiede ulteriori modifiche al codice al di fuori delle modifiche nella dichiarazione Enum. Se aggiungi un nuovo elemento all'enum, myEnum = myEnumValues[i]restituirà comunque l' ielemento th nell'enum senza modifiche.
Dandre Allison,

45

Se vuoi dare i tuoi valori interi, puoi usare una struttura come sotto

public enum A
{
        B(0),
        C(10),
        None(11);
        int id;
        private A(int i){id = i;}

        public int GetID(){return id;}
        public boolean IsEmpty(){return this.equals(A.None);}
        public boolean Compare(int i){return id == i;}
        public static A GetValue(int _id)
        {
            A[] As = A.values();
            for(int i = 0; i < As.length; i++)
            {
                if(As[i].Compare(_id))
                    return As[i];
            }
            return A.None;
        }
}

5
+1 perché evidenzia il fatto che i valori non devono essere consecutivi.
rhavin,

Inoltre, questa sembra essere l'unica risposta che funziona se si desidera un insieme sparso (o ripetuto) di valori interi anziché utilizzare gli ordinali predefiniti da 0 .. (count-1). Questo può essere importante se stai interagendo con codice esistente, ad esempio su una rete.
benkc,

Vale la pena sottolineare che, come nelle risposte precedenti, è probabilmente utile memorizzare nella cache il risultato di valori (), in modo da evitare un'allocazione di memoria e una matrice ogni volta che la si invoca.
benkc,

1
E, a seconda della lunghezza del tuo enum, potresti voler creare una HashMap o usare una ricerca binaria o qualcosa per questa ricerca, piuttosto che fare una ricerca lineare ogni volta.
benkc,

2
Questo dovrebbe essere il modo giusto e la migliore pratica per convertire int in enum e penso che tu possa semplificare il problema con A GetValue statico pubblico (int _id) {for (A a: A.values ​​() {if (a.getId ( ) == _ id) {return a;}} return null;} Sbarazzati delle cose None, isEmpty () e compare ().
Chris.Zou,

35

Puoi provare così.
Crea classe con ID elemento.

      public Enum MyEnum {
        THIS(5),
        THAT(16),
        THE_OTHER(35);

        private int id; // Could be other data type besides int
        private MyEnum(int id) {
            this.id = id;
        }

        public static MyEnum fromId(int id) {
                for (MyEnum type : values()) {
                    if (type.getId() == id) {
                        return type;
                    }
                }
                return null;
            }
      }

Ora prendi questo Enum usando id come int.

MyEnum myEnum = MyEnum.fromId(5);

19

Memorizzo nella cache i valori e creo un semplice metodo di accesso statico:

public static enum EnumAttributeType {
    ENUM_1,
    ENUM_2;
    private static EnumAttributeType[] values = null;
    public static EnumAttributeType fromInt(int i) {
        if(EnumAttributeType.values == null) {
            EnumAttributeType.values = EnumAttributeType.values();
        }
        return EnumAttributeType.values[i];
    }
}

4
Questa è la soluzione che uso ora. Ma IMHO è meno confuso se non si assegna al campo valueslo stesso nome del metodo values(). Io uso cachedValuesper il nome del campo.
ToolmakerSteve

2
Efficiente ed elegante; copiato e incollato nel mio progetto :) L'unica cosa che ho cambiato è fromInt (int i), che sono appena stato chiamato da (int i) perché è un po 'ridondante avere int due volte nella firma.
pipedreambomb,

1
perché non inizializzare dall'inizio? perché aspettare il primo colpo?
Kaiser,

9

Gli enum Java non hanno lo stesso tipo di mapping enum-to-int che hanno in C ++.

Detto questo, tutti gli enum hanno un valuesmetodo che restituisce una matrice di possibili valori enum, quindi

MyEnum enumValue = MyEnum.values()[x];

dovrebbe funzionare. È un po 'brutto e potrebbe essere meglio non provare a convertire da ints a Enums (o viceversa) se possibile.


9

Questo non è qualcosa che di solito viene fatto, quindi riconsidererei. Detto questo, le operazioni fondamentali sono: int -> enum usando EnumType.values ​​() [intNum] e enum -> int usando enumInst.ordinal ().

Tuttavia, poiché qualsiasi implementazione di valori () non ha altra scelta che darti una copia dell'array (gli array java non sono mai di sola lettura), verrai servito meglio usando una EnumMap per memorizzare nella cache il mapping enum -> int.


2
Ri "Questo non è qualcosa che di solito viene fatto": Caso comune in cui è utile: enum corrisponde ai valori int memorizzati in un database.
ToolmakerSteve

@ToolmakerSteve hai perfettamente ragione sul fatto che i mapping sono richiesti. Ma vorresti lasciare quel tipo di codifica a qualche mappatore OR o qualche toolkit / libreria?
Dilum Ranatunga,


6

Ecco la soluzione con cui ho intenzione di andare. Questo non funziona solo con numeri interi non sequenziali, ma dovrebbe funzionare con qualsiasi altro tipo di dati che potresti voler usare come ID sottostante per i tuoi valori enum.

public Enum MyEnum {
    THIS(5),
    THAT(16),
    THE_OTHER(35);

    private int id; // Could be other data type besides int
    private MyEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return this.id;
    }

    public static Map<Integer, MyEnum> buildMap() {
        Map<Integer, MyEnum> map = new HashMap<Integer, MyEnum>();
        MyEnum[] values = MyEnum.values();
        for (MyEnum value : values) {
            map.put(value.getId(), value);
        }

        return map;
    }
}

Devo solo convertire gli ID in enum in momenti specifici (durante il caricamento di dati da un file), quindi non c'è motivo per me di mantenere la mappa in memoria in ogni momento. Se è necessario che la mappa sia sempre accessibile, è sempre possibile memorizzarla nella cache come membro statico della classe Enum.


Se fossi preoccupato per l'uso della memoria, avrei creato dinamicamente HashMap - come @ossys ma con un codice diverso quando la cache è nulla, quindi aggiungere un secondo metodo clearCachedValuesquando hai finito di usarlo (che riporta il campo privato su null). Considero MyEnum.fromInt(i)più facile da capire che passare intorno a un oggetto mappa.
ToolmakerSteve

6

Nel caso in cui aiuti gli altri, l'opzione che preferisco, che non è elencata qui, utilizza la funzionalità Mappe di Guava :

public enum MyEnum {
    OPTION_1(-66),
    OPTION_2(32);

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

    public int getValue() {
        return this.value;
    }

    private static ImmutableMap<Integer, MyEnum> reverseLookup = 
            Maps.uniqueIndex(Arrays.asList(MyEnum.values())), MyEnum::getValue);

    public static MyEnum fromInt(final int id) {
        return reverseLookup.getOrDefault(id, OPTION_1);
    }
}

Con l'impostazione predefinita che puoi utilizzare null, puoi throw IllegalArgumentExceptiono puoi fromIntrestituire un Optionalcomportamento qualunque tu preferisca.


3
Dovresti dire che stai usando Guava. Oppure puoi usare i flussi:Map<Integer, MyEnum> reverseLookup = Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::getValue, Function.identity()));
shmosel

1
Potrebbe voler definire anche un getValue()metodo.
shmosel,

@shmosel Spiacenti, ho perso la funzione getValue, grazie. La digitazione di versioni generiche in StackOverflow non esegue sempre il pan out. Aggiunto commento sull'uso di Guava con un collegamento. Preferisci il metodo Guava ai flussi.
Chad Befus,

4

Puoi iterare su values()enum e confrontare il valore intero di enum con dato idcome di seguito:

public enum  TestEnum {
    None(0),
    Value1(1),
    Value2(2),
    Value3(3),
    Value4(4),
    Value5(5);

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

    public int getValue() {
        return value;
    }

    public static TestEnum  getEnum(int value){
        for (TestEnum e:TestEnum.values()) {
            if(e.getValue() == value)
                return e;
        }
        return TestEnum.None;//For values out of enum scope
    }
}

E usa così:
TestEnum x = TestEnum.getEnum(4);//Will return TestEnum.Value4
spero che questo aiuti;)


3

Sulla base della risposta di @ChadBefus e del commento di @shmosel, consiglierei di usare questo. (Ricerca efficiente e funziona su Java puro> = 8)

import java.util.stream.Collectors;
import java.util.function.Function;
import java.util.Map;
import java.util.Arrays;

public enum MyEnum {
    OPTION_1(-66),
    OPTION_2(32);

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

    public int getValue() {
        return this.value;
    }

    private static Map<Integer, MyEnum> reverseLookup =
        Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::getValue, Function.identity()));

    public static MyEnum fromInt(final int id) {
        return reverseLookup.getOrDefault(id, OPTION_1);
    }
    public static void main(String[] args) {
        System.out.println(fromInt(-66).toString());
    }
}

0

Una buona opzione è quella di evitare la conversione da intaenum : ad esempio, se hai bisogno del valore massimo, puoi confrontare x.ordinal () a y.ordinal () e restituire x o y di conseguenza. (Potrebbe essere necessario riordinare i valori per rendere significativo tale confronto.)

Se ciò non fosse possibile, memorizzerei MyEnum.values()in un array statico.


1
Se ottieni int da DB e vuoi convertirlo in Enum, che penso sia un compito molto comune, avrai bisogno di int -> conversione enum, IMO ..
Yuki Inoue,

0

Questa è la stessa risposta dei medici ma mostra come eliminare il problema con array mutabili. Se si utilizza questo tipo di approccio a causa della previsione della diramazione, se si avrà un effetto minimo o nullo e l'intero codice chiama i valori di matrice mutabili () solo una volta. Poiché entrambe le variabili sono statiche, non consumeranno n * memoria per ogni utilizzo di questa enumerazione.

private static boolean arrayCreated = false;
private static RFMsgType[] ArrayOfValues;

public static RFMsgType GetMsgTypeFromValue(int MessageID) {
    if (arrayCreated == false) {
        ArrayOfValues = RFMsgType.values();
    }

    for (int i = 0; i < ArrayOfValues.length; i++) {
        if (ArrayOfValues[i].MessageIDValue == MessageID) {
            return ArrayOfValues[i];
        }
    }
    return RFMsgType.UNKNOWN;
}

0
enum MyEnum {
    A(0),
    B(1);
    private final int value;
    private MyEnum(int val) {this.value = value;}
    private static final MyEnum[] values = MyEnum.values();//cache for optimization
    public static final getMyEnum(int value) { 
        try {
            return values[value];//OOB might get triggered
        } catch (ArrayOutOfBoundsException e) {
        } finally {
            return myDefaultEnumValue;
        }
    }
}


@shmosel Credo che le eccezioni siano significativamente migliori se i limiti sono relativamente pochi.
Zoso,

Su cosa si basa la tua convinzione?
shmosel,

0

A Kotlin:

enum class Status(val id: Int) {
    NEW(0), VISIT(1), IN_WORK(2), FINISHED(3), CANCELLED(4), DUMMY(5);

    companion object {
        private val statuses = Status.values().associateBy(Status::id)

        fun getStatus(id: Int): Status? = statuses[id]
    }
}

Uso:

val status = Status.getStatus(1)!!

0

Ha scritto questa implementazione. Consente valori mancanti, valori negativi e mantiene coerente il codice. Anche la mappa è memorizzata nella cache. Utilizza un'interfaccia e necessita di Java 8.

enum

public enum Command implements OrdinalEnum{
    PRINT_FOO(-7),
    PRINT_BAR(6),
    PRINT_BAZ(4);

    private int val;
    private Command(int val){
        this.val = val;
    }

    public int getVal(){
        return val;
    }

    private static Map<Integer, Command> map = OrdinalEnum.getValues(Command.class);
    public static Command from(int i){
        return map.get(i);
    }
}

Interfaccia

public interface OrdinalEnum{
    public int getVal();

    @SuppressWarnings("unchecked")
    static <E extends Enum<E>> Map<Integer, E> getValues(Class<E> clzz){
        Map<Integer, E> m = new HashMap<>();
        for(Enum<E> e : EnumSet.allOf(clzz))
            m.put(((OrdinalEnum)e).getVal(), (E)e);

        return m;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.