Rzuć Int na enum w Javie


331

Jaki jest prawidłowy sposób rzutowania Int na wyliczenie w Javie, biorąc pod uwagę następujący wyliczenie?

public enum MyEnum
{
    EnumValue1,
    EnumValue2
}


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

@RyuS. To nie jest kopia tego pytania
Mark Rotteveel

Odpowiedzi:


585

Spróbuj MyEnum.values()[x]gdzie xmusi być 0lub 1, tj. Prawidłowy porządek dla tego wyliczenia.

Zauważ, że w Javie wyliczenia są w rzeczywistości klasami (a wartości wyliczenia są zatem obiektami), a zatem nie można rzutować intani nawet Integerna wyliczenie.


116
+1: Możesz chcieć buforować, MyEnum.values()ponieważ jest drogi. tzn. jeśli nazwiesz to setki razy.
Peter Lawrey

6
@PeterLawrey Tylko dla kompletności, czy możesz wyjaśnić, dlaczego powinien być wolny? Nie widzę wyraźnego powodu.
Tarrasch

48
@Tarrasch, ponieważ tablice są zmienne, wartości () muszą zwracać kopię tablicy elementów na wypadek, gdybyś ją zmienił. Tworzenie tej kopii za każdym razem jest stosunkowo drogie.
Peter Lawrey,

9
@PeterLawrey Ostatnio używałem dużo haskell (gdzie wszystko jest niezmienne)! Dziękujemy za jasne i zwięzłe wyjaśnienie. :)
Tarrasch

jak zdobyć „x”?
Beeing Jk

161

MyEnum.values()[x]jest kosztowną operacją. Jeśli wydajność jest problemem, możesz chcieć zrobić coś takiego:

public enum MyEnum {
    EnumValue1,
    EnumValue2;

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

44
Jeśli chcesz uniknąć konserwacji przełącznika, skorzystaj z klasy using: private final MyEnum [] myEnumValues ​​= MyEnum.values ​​(); Następnie użycie: myEnum = myEnumValues ​​[i];
Gili Nachum

23
@GiliNachum powiedział to w dziwny sposób, ale problemem z tym rozwiązaniem jest łatwość konserwacji. Jest to niezgodne z zasadą OSUSZANIA, co oznacza, że ​​przy każdej zmianie wartości wyliczenia (zmiana kolejności, wartość (wartości) dodane, wartość (wartości) usunięte) instrukcja switch musi być jednocześnie aktualizowana. Komentarz Gili zmusza Javę do zachowania spójności z listą wartości wyliczeniowych, zmiany wartości wyliczeniowych w ogóle nie wpływają na to podejście.
Dandre Allison

3
@LorenzoPolidori, Czy możesz wyjaśnić, dlaczego uważasz MyEnum.values()[x]za kosztowną operację. Nie wiem, jak to działa w szczegółach, ale wydaje mi się, że dostęp do elementu w tablicy nie byłby niczym wielkim, czyli ciągłym czasem. Jeśli tablica musi zostać skompilowana, zajmuje to O (n) czas, co jest tym samym czasem uruchamiania, co rozwiązanie.
brunsgaard

1
@brunsgaard Zakładam, że values()generuje nową tablicę za każdym razem, ponieważ tablice są zmienne, więc wielokrotne zwracanie tego samego nie byłoby bezpieczne. Instrukcje przełączania niekoniecznie są O (n), można je skompilować, aby przejść do tabel. Więc twierdzenia Lorenza wydają się uzasadnione.
MikeFHay

1
Wersja @Johnson Gili nie wymaga żadnych dodatkowych zmian kodu poza zmianami w deklaracji Enum. Jeśli dodasz nowy element do wyliczenia, myEnum = myEnumValues[i]nadal zwróci ten ielement w wyliczeniu bez zmian.
Dandre Allison

45

Jeśli chcesz podać swoje liczby całkowite, możesz użyć struktury takiej jak poniżej

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, ponieważ podkreśla fakt, że wartości nie muszą być następujące po sobie.
rhavin

Ponadto wydaje się, że jest to jedyna odpowiedź, która działa, jeśli chcesz rzadki (lub powtarzany) zestaw wartości całkowitych zamiast używać domyślnych rzędnych od 0 .. (liczba-1). Może to być ważne, jeśli wchodzisz w interakcję z istniejącym kodem, na przykład przez sieć.
benkc

Warto zauważyć, że jak w powyższych odpowiedziach, buforowanie wyniku wartości () jest prawdopodobnie warte zachodu, aby uniknąć alokacji pamięci i tworzenia kopii zapasowych za każdym razem, gdy ją wywołujesz.
benkc

1
I, w zależności od długości twojego wyliczenia, możesz chcieć utworzyć HashMap lub użyć wyszukiwania binarnego lub czegoś takiego dla tego wyszukiwania, zamiast przeprowadzania wyszukiwania liniowego za każdym razem.
benkc

2
Powinien to być właściwy sposób i najlepsza praktyka konwersji int na wyliczanie i myślę, że można uprościć problem przez publiczne statyczne A GetValue (int _id) {for (A a: A.values ​​() {if (a.getId ( ) == _ id) {return a;}} return null;} Pozbądź się rzeczy None, isEmpty () i porównaj ().
Chris.Zou

35

Możesz spróbować w ten sposób.
Utwórz klasę o identyfikatorze elementu.

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

Teraz pobierz to Enum używając id jako int.

MyEnum myEnum = MyEnum.fromId(5);

19

Zapisuję wartości w pamięci podręcznej i tworzę prostą metodę dostępu statycznego:

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
Z tego rozwiązania korzystam teraz. Ale IMHO jest mniej mylące, jeśli nie nadasz polu valuestej samej nazwy co metoda values(). Używam cachedValuesnazwy pola.
ToolmakerSteve

2
Wydajny i elegancki; skopiowane i wklejone do mojego projektu :) Jedyną rzeczą, którą zmieniłem, jest fromInt (int i), którą właśnie wywołałem z (int i), ponieważ jest trochę zbędne, aby mieć int dwa razy w podpisie.
pipedreambomb

1
dlaczego nie zainicjować od początku? po co czekać na pierwszy hit?
kaiser

9

Wyliczenia Java nie mają tego samego rodzaju odwzorowania wyliczania na int, co w C ++.

To powiedziawszy, wszystkie valueswyliczenia mają metodę, która zwraca tablicę możliwych wartości wyliczenia, więc

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

powinno działać. Jest to trochę nieprzyjemne i lepiej nie próbować konwertować z ints na Enums (lub odwrotnie), jeśli to możliwe.


9

To nie jest coś, co zwykle się robi, więc ponownie się zastanowię. Ale powiedziawszy to, podstawowymi operacjami są: int -> enum przy użyciu EnumType.values ​​() [intNum] oraz enum -> int przy użyciu enumInst.ordinal ().

Ponieważ jednak jakakolwiek implementacja wartości () nie ma innego wyjścia, jak dać kopię tablicy (tablice java nigdy nie są tylko do odczytu), lepiej byłoby użyć EnumMap do buforowania odwzorowania enum -> int.


2
Odp: „To nie jest coś, co zwykle się robi”: częsty przypadek, w którym jest to przydatne: enum odpowiada wartościom int przechowywanym w bazie danych.
ToolmakerSteve

@ToolmakerSteve masz całkowitą rację, że mapowania są wymagane. Ale czy chcesz pozostawić tego rodzaju kodowanie jakiejś programowi odwzorowującemu OR lub zestawowi narzędzi / bibliotece?
Dilum Ranatunga,


6

Oto rozwiązanie, z którym planuję pójść. Działa to nie tylko z niesekwencyjnymi liczbami całkowitymi, ale powinno współpracować z każdym innym typem danych, którego możesz chcieć użyć jako podstawowego identyfikatora dla swoich wartości wyliczeniowych.

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

Muszę tylko przekonwertować id na wyliczenia w określonych momentach (podczas ładowania danych z pliku), więc nie mam powodu, aby zawsze przechowywać Mapę w pamięci. Jeśli potrzebujesz, aby mapa była zawsze dostępna, zawsze możesz buforować ją jako statyczny element klasy Enum.


IMHO, gdybym martwił się wykorzystaniem pamięci, dynamicznie tworzyłbym HashMap - jak @ossys, ale z innym kodem, gdy pamięć podręczna jest pusta, a następnie dodaje drugą metodę, clearCachedValuesgdy skończysz z niej korzystać (która ustawia prywatne pole z powrotem na null). Uważam, że MyEnum.fromInt(i)łatwiej to zrozumieć niż omijanie obiektu mapy.
ToolmakerSteve

6

W przypadku, gdy pomaga innym, preferowana przeze mnie opcja, która nie jest tutaj wymieniona, korzysta z funkcji Map 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);
    }
}

Z domyślnym można użyć null, można throw IllegalArgumentExceptionalbo Twój fromIntmogła zwracać Optional, co zachowanie wolisz.


3
Powinieneś wspomnieć, że używasz Guawy. Lub możesz użyć strumieni:Map<Integer, MyEnum> reverseLookup = Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::getValue, Function.identity()));
shmosel

1
Może chcesz również zdefiniować getValue()metodę.
shmosel

@shmosel Ups, brakowało mi funkcji getValue, dzięki. Wpisywanie wersji ogólnych w stosie przepływu nie zawsze się sprawdza. Dodano komentarz na temat korzystania z Guava z linkiem. Preferuj metodę Guava od strumieni.
Czad Befus

4

Możesz iterować values()wyliczanie i porównywać wartość całkowitą wyliczenia z podanymi idjak poniżej:

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

I użyj tego w następujący sposób:
TestEnum x = TestEnum.getEnum(4);//Will return TestEnum.Value4
mam nadzieję, że to pomoże;)


3

Opierając się na odpowiedzi @ChadBefus i komentarzu @shmosel, polecam użyć tego. (Skuteczne wyszukiwanie i działa na czystej Javie> = 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

Dobrą opcją jest uniknięcie konwersji z intna enum: na przykład, jeśli potrzebujesz maksymalnej wartości, możesz porównać x.ordinal () z y.ordinal () i odpowiednio zwrócić x lub y. (Może być konieczne ponowne uporządkowanie wartości, aby takie porównanie miało sens).

Jeśli nie jest to możliwe, zapisałbym MyEnum.values()w tablicy statycznej.


1
Jeśli dostaniesz int z DB i chcesz przekonwertować go na Enum, co moim zdaniem jest bardzo częstym zadaniem, będziesz potrzebować int -> enum konwersji, IMO ..
Yuki Inoue

0

Jest to ta sama odpowiedź, co lekarze, ale pokazuje, jak wyeliminować problem ze zmiennymi tablicami. Jeśli zastosujesz takie podejście ze względu na przewidywanie gałęzi, jeśli będzie miało bardzo niewielki do zera efekt, a cały kod wywoła funkcję tablic zmiennych () tylko raz. Ponieważ obie zmienne są statyczne, nie będą zużywać pamięci n * dla każdego użycia tego wyliczenia.

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 Uważam, że wyjątki są znacznie lepsze, jeśli poza zakresem jest stosunkowo niewiele.
Zoso,

Na czym opiera się twoje przekonanie?
shmosel,

0

W Kotlinie:

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

Stosowanie:

val status = Status.getStatus(1)!!

0

Napisałem tę implementację. Pozwala to na brakujące wartości, wartości ujemne i utrzymuje spójność kodu. Mapa jest również buforowana. Korzysta z interfejsu i wymaga 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);
    }
}

Berło

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;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.