Приведите Int для перечисления в Java


Ответы:


588

Попробуйте, MyEnum.values()[x]где xдолжно быть 0или 1, то есть действительный порядковый номер для этого перечисления.

Обратите внимание, что в Java перечисления на самом деле являются классами (и значения перечисления, таким образом, являются объектами), и, таким образом, вы не можете привести intили даже Integerперечислить.


117
+1: вы можете захотеть кэшировать MyEnum.values()как его дорого. т.е. если вы звоните это сотни раз.
Питер Лори

6
@PeterLawrey Только для полноты, вы можете объяснить, почему это должно быть медленно? Я не вижу очевидной причины для этого.
Tarrasch

48
@Tarrasch, поскольку массивы являются изменяемыми, values ​​() должен возвращать копию массива элементов на случай, если вам случится изменить его. Создание этой копии каждый раз относительно дорого.
Питер Лори

9
@PeterLawrey В последнее время я много пользуюсь хаскелем (где все неизменно)! Спасибо за ваше ясное и краткое объяснение. :)
Tarrasch

как получить «х»?
Beeing Jk

161

MyEnum.values()[x]это дорогая операция. Если производительность вызывает беспокойство, вы можете сделать что-то вроде этого:

public enum MyEnum {
    EnumValue1,
    EnumValue2;

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

44
Если вы хотите избежать обслуживания коммутатора, то используйте класс using: private final MyEnum [] myEnumValues ​​= MyEnum.values ​​(); Тогда использование: myEnum = myEnumValues ​​[i];
Гили Нахум

23
@GiliNachum сказал это странным образом, но проблема с этим решением - ремонтопригодность. Это идет вразрез с принципом DRY, который означает, что при изменении значений перечисления (переупорядочении, добавлении значений, удалении значений) оператор switch должен обновляться одновременно. Комментарий Гили вынуждает Java поддерживать согласованность со списком значений перечисления, изменения значений перечисления не влияют на этот подход вообще.
Дандре Эллисон

3
@LorenzoPolidori, Можете ли вы объяснить, почему вы считаете MyEnum.values()[x]дорогой операцией. Я не знаю, как это работает в деталях, но мне кажется, что доступ к элементу в массиве не будет большой проблемой, то есть постоянным временем. Если массив должен быть собран, это занимает O (n) время, то есть то же время выполнения, что и ваше решение.
Brunsgaard

1
@brunsgaard Я полагаю, values()каждый раз генерируется новый массив, потому что массивы изменчивы, поэтому было бы небезопасно возвращать один и тот же один раз несколько раз. Операторы switch не обязательно O (n), они могут быть скомпилированы для перехода по таблицам. Таким образом, претензии Лоренцо кажутся оправданными.
MikeFHay

1
Версия @Johnson Gili не требует каких-либо дополнительных изменений кода, кроме изменений в объявлении Enum. Если вы добавите новый элемент в enum, myEnum = myEnumValues[i]все равно вернете этот iэлемент в Enum без изменений.
Дандре Эллисон

45

Если вы хотите дать целочисленные значения, вы можете использовать структуру, как показано ниже

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, потому что это подчеркивает тот факт, что значения не должны быть последовательными.
Равин

Кроме того, это, кажется, единственный ответ, который работает, если вы хотите разреженный (или повторяющийся) набор целочисленных значений вместо использования порядковых номеров по умолчанию от 0 .. (count-1). Это может быть важно, если вы взаимодействуете с существующим кодом, например, по сети.
benkc

Стоит отметить, что, как и в приведенных выше ответах, кэширование результата values ​​(), вероятно, имеет смысл, чтобы избежать выделения памяти и копирования массива при каждом его вызове.
benkc

1
И, в зависимости от длины вашего перечисления, вы можете захотеть создать HashMap или использовать бинарный поиск или что-то для этого поиска, вместо того, чтобы выполнять линейный поиск каждый раз.
benkc

2
Это должно быть правильным способом и наилучшей практикой для преобразования int в enum, и я думаю, что вы можете упростить проблему с помощью общедоступной статической функции GetValue (int _id) {for (A a: A.values ​​() {if (a.getId ( ) == _ id) {return a;}} return null;} Избавьтесь от None, isEmpty () и compare ()
Chris.Zou

35

Вы можете попробовать вот так.
Создать класс с идентификатором элемента.

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

Теперь выберите этот Enum, используя id как int.

MyEnum myEnum = MyEnum.fromId(5);

19

Я кеширую значения и создаю простой метод статического доступа:

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
Это решение я использую сейчас. Но ИМХО это менее запутанно, если вы не дадите полю valuesто же имя, что и метод values(). Я использую cachedValuesдля имени поля.
ToolmakerSteve

2
Эффективный и элегантный; скопировал и вставил в мой проект :) Единственное, что я изменил, это fromInt (int i), который я только что вызвал из (int i), потому что немного избыточно иметь int дважды в сигнатуре.
pipedreambomb

1
почему не инициализировать с начала? зачем ждать первого удара?
Кайзер

9

Перечисления Java не имеют того же типа сопоставления перечисления с целым, что и в C ++.

Тем не менее, все перечисления имеют valuesметод, который возвращает массив возможных значений перечисления, поэтому

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

должно сработать. Это немного неприятно, и, возможно, лучше не пытаться конвертировать из ints в Enums (или наоборот), если это возможно.


9

Это не то, что обычно делается, поэтому я бы пересмотрел. Но, сказав это, основными операциями являются: int -> enum с использованием EnumType.values ​​() [intNum] и enum -> int с использованием enumInst.ordinal ().

Однако, поскольку любая реализация values ​​() не имеет другого выбора, кроме как предоставить вам копию массива (java-массивы никогда не доступны только для чтения), вам лучше использовать EnumMap для кэширования отображения enum -> int.


2
Re "Это не то, что обычно делается": Общий случай, когда это полезно: enum соответствует значениям int, хранящимся в базе данных.
ToolmakerSteve

@ ToolmakerSteve вы абсолютно правы в том, что сопоставления необходимы. Но хотите ли вы оставить такую ​​кодировку для какого-либо средства отображения или набора инструментов / библиотеки?
Дилум Ранатунга


6

Вот решение, которое я планирую использовать. Это не только работает с непоследовательными целыми числами, но и должно работать с любым другим типом данных, который вы можете использовать в качестве основного идентификатора для значений перечисления.

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

Мне нужно только конвертировать идентификаторы в перечисления в определенное время (при загрузке данных из файла), поэтому у меня нет причин постоянно хранить карту в памяти. Если вам все время нужна карта, вы всегда можете кэшировать ее как статический член вашего класса Enum.


ИМХО, если бы меня беспокоило использование памяти, я бы динамически создал HashMap-подобный @ossys, но с другим кодом, когда кеш нулевой, а затем добавил бы второй метод, clearCachedValuesкогда вы закончили с его использованием (который устанавливает приватное поле обратно в ноль). Я считаю, что MyEnum.fromInt(i)легче понять, чем обойти объект карты.
ToolmakerSteve

6

В случае, если это помогает другим, предпочтительный вариант, которого здесь нет в списке, использует функциональность карт 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);
    }
}

По умолчанию вы можете использовать null, вы можете throw IllegalArgumentExceptionили можете fromIntвернуть Optionalлюбое поведение, которое вы предпочитаете.


3
Вы должны упомянуть, что вы используете гуаву. Или вы можете использовать потоки:Map<Integer, MyEnum> reverseLookup = Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::getValue, Function.identity()));
shmosel

1
Возможно, захотите определить getValue()метод также.
Шмосел

@shmosel Упс, я пропустил функцию getValue, спасибо. Ввод общих версий в stackoverflow не всегда удастся. Добавлен комментарий об использовании гуавы со ссылкой. Предпочитаю метод Гуава для потоков.
Чад Бефус

4

Вы можете перебирать values()enum и сравнивать целочисленные значения enum с данными, idкак показано ниже:

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

И используйте так:
TestEnum x = TestEnum.getEnum(4);//Will return TestEnum.Value4
надеюсь, это поможет;)


3

Основываясь на ответе @ChadBefus и комментарии @shmosel, я бы рекомендовал использовать это. (Эффективный поиск и работает на чистой Java> = 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

Хороший вариант - избегать конвертации intвenum : например, если вам нужно максимальное значение, вы можете сравнить x.ordinal () с y.ordinal () и вернуть x или y соответственно. (Возможно, вам придется изменить порядок значений, чтобы сделать такое сравнение значимым.)

Если это невозможно, я бы MyEnum.values()сохранил статический массив.


1
Если вы получаете int из DB и хотите преобразовать его в Enum, что, как мне кажется, является очень распространенной задачей, вам потребуется преобразование int -> enum, IMO ..
Yuki Inoue

0

Это тот же ответ, что и у врачей, но он показывает, как устранить проблему с изменяемыми массивами. Если вы используете такой подход сначала из-за предсказания ветвлений, если эффект будет иметь очень малое значение до нуля, а весь код вызывает функцию изменяемого массива values ​​() только один раз. Поскольку обе переменные являются статическими, они не будут использовать n * памяти для каждого использования этого перечисления.

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 Я полагаю, что исключения значительно лучше, если выход за пределы относительно немного.
Зосо

На чем основана ваша вера?
шмосел

0

В Котлине:

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

Использование:

val status = Status.getStatus(1)!!

0

Написал эту реализацию. Он учитывает пропущенные значения, отрицательные значения и поддерживает согласованность кода. Карта также кэшируется. Использует интерфейс и нуждается в 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);
    }
}

Интерфейс

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;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.