Преобразовать из порядкового номера перечисления в тип перечисления

316

У меня есть тип enum, ReportTypeEnumкоторый передается между методами во всех моих классах, но затем мне нужно передать это по URL-адресу, чтобы я использовал порядковый метод для получения значения int. После того, как я получу его на другой моей странице JSP, мне нужно преобразовать его обратно в формат, ReportTypeEnumчтобы я мог продолжить его передачу.

Как я могу преобразовать порядковый номер в ReportTypeEnum?

Использование Java 6 SE.

Ленни
источник
1
До сих пор нет Java 6 EE (AFAIK). Существует Java SE 6 и Java EE 5.
Хосам Али

Ответы:

632

Чтобы преобразовать порядковый номер в его представление enum, вы можете сделать это:

ReportTypeEnum value = ReportTypeEnum.values()[ordinal];

Пожалуйста, обратите внимание на границы массива.

Обратите внимание, что каждый вызов values()возвращает новый клонированный массив, который может отрицательно повлиять на производительность. Вы можете кэшировать массив, если он будет часто вызываться.

Пример кода о том, как кешироватьvalues() .


Этот ответ был отредактирован, чтобы включить обратную связь, данную внутри комментариев

Йоахим Зауэр
источник
Я реализовал это решение, и оно не работает для меня. Он возвращает порядковое значение, которое не гарантирует совпадения с порядком добавления перечислимых типов. Я не знаю, что именно этот ответ защищает, но я все же хотел предупредить людей
IcedDante
@IcesDante: порядковый номер определенно гарантированно соответствует порядку значений перечисления в источнике. Если вы наблюдаете другое поведение, значит что-то еще должно быть не так. Мой ответ выше, однако, неоптимален по всем причинам, изложенным в других ответах.
Иоахим Зауэр
@JoachimSauer Возможно IcedDante означает, что порядковый номер может не совпадать, если он был создан и сохранен более ранней версией источника, в которой значения перечисления были в другом порядке.
LarsH
137

Это почти наверняка плохая идея . Конечно, если порядковый номер де-факто сохраняется (например, потому что кто-то добавил закладку в URL) - это означает, что вы всегда должны сохранять enumпорядок в будущем, что может быть неочевидно для сопровождающих кода в дальнейшем.

Почему бы не кодировать enumиспользование myEnumValue.name()(и декодировать через ReportTypeEnum.valueOf(s)) вместо этого?

oxbow_lakes
источник
24
Что если вы измените имя enum (но сохраните порядок)?
Арне Эвертссон
6
@Arne - я думаю, что это гораздо менее вероятно, чем какой-то неопытный человек, который придет и добавит valueв начале или в правильной алфавитной / логической позиции. (Под логическим я имею в виду, например, TimeUnitзначения имеют логическую позицию)
oxbow_lakes
7
Я, конечно, предпочитаю устанавливать порядок перечислений, а не имя моего перечисления ... вот почему я предпочитаю хранить порядковый номер, а не имя перечисления в базе данных. Кроме того, лучше использовать int-манипуляции, а не String ...
8
Я согласен. В общедоступном API изменение имени Enum нарушит обратную совместимость, но изменение порядка не будет. По этой причине более разумно использовать имя в качестве своего «ключа»
Ноэль
3
Хранение порядкового номера облегчает перевод ваших идей на другие языки. Что если вам нужно написать какой-нибудь компонент на C?
QED
94

Если я собираюсь использовать values()много:

enum Suit {
   Hearts, Diamonds, Spades, Clubs;
   public static final Suit values[] = values();
}

Тем временем где.java:

Suit suit = Suit.values[ordinal];

Следите за границами вашего массива.

QED
источник
3
+1 это, безусловно, лучшее решение ИМХО, потому что можно передавать ординалы, особенно в android.os.Message.
likejudo
9
Это очень беспокоит, потому что массивы изменчивы . Несмотря на то, что значения [] является окончательным, это не мешает Suit.values[0] = Suit.Diamonds;где-то в вашем коде. В идеале этого никогда не случится, но общий принцип не выставлять изменяемые поля все еще сохраняется. Для этого подхода рассмотрите использование Collections.unmodifiableListили подобное вместо этого.
Мшник
Как насчет - частные статические окончательные значения Suit [] = values ​​(); public static Suit [] getValues ​​() {возвращаемые значения; }
Пратюш
4
@Pratyush, который сделал бы переменную массива неизменной, но не ее содержимое. Я все еще мог бы сделать getValues ​​() [0] = что-тоElse;
Калабацин
Согласитесь, таким образом, точка не раскрывает Suit values[]прямо или косвенно (как было упомянуто через getValues()), я работаю с открытым методом, где ordinalзначение должно быть отправлено как аргумент и вернуть Suitпредставление из Suit values[]. Суть здесь (фрагмент вопроса с самого начала) заключалась в создании типа enum из порядкового номера enum
Мануэль Джордан
13

Я согласен с большинством людей, что использование порядкового номера, вероятно, плохая идея. Обычно я решаю эту проблему, предоставляя enum приватный конструктор, который может принимать, например, значение DB, а затем создавать статическую fromDbValueфункцию, аналогичную той, что была в ответе Яна.

public enum ReportTypeEnum {
    R1(1),
    R2(2),
    R3(3),
    R4(4),
    R5(5),
    R6(6),
    R7(7),
    R8(8);

    private static Logger log = LoggerFactory.getLogger(ReportEnumType.class);  
    private static Map<Integer, ReportTypeEnum> lookup;
    private Integer dbValue;

    private ReportTypeEnum(Integer dbValue) {
        this.dbValue = dbValue;
    }


    static {
        try {
            ReportTypeEnum[] vals = ReportTypeEnum.values();
            lookup = new HashMap<Integer, ReportTypeEnum>(vals.length);

            for (ReportTypeEnum  rpt: vals)
                lookup.put(rpt.getDbValue(), rpt);
         }
         catch (Exception e) {
             // Careful, if any exception is thrown out of a static block, the class
             // won't be initialized
             log.error("Unexpected exception initializing " + ReportTypeEnum.class, e);
         }
    }

    public static ReportTypeEnum fromDbValue(Integer dbValue) {
        return lookup.get(dbValue);
    }

    public Integer getDbValue() {
        return this.dbValue;
    }

}

Теперь вы можете изменить порядок без изменения поиска и наоборот.

jmkelm08
источник
Это правильный ответ. Я удивлен, что получил так мало баллов по сравнению с другими более прямыми, но потенциально неверными ответами (из-за изменений кода в будущем)
Калабацин
8

Вы можете использовать статическую таблицу поиска:

public enum Suit {
  spades, hearts, diamonds, clubs;

  private static final Map<Integer, Suit> lookup = new HashMap<Integer, Suit>();

  static{
    int ordinal = 0;
    for (Suit suit : EnumSet.allOf(Suit.class)) {
      lookup.put(ordinal, suit);
      ordinal+= 1;
    }
  }

  public Suit fromOrdinal(int ordinal) {
    return lookup.get(ordinal);
  }
}
январь
источник
3
Смотрите также Enums .
trashgod
11
Вот Это Да! Просто вау! Конечно, это здорово, но ... вы знаете - программист на C внутри меня кричит от боли, видя, что вы выделяете полноценный HashMap и выполняете поиск внутри всего этого ПРОСТО, чтобы по существу управлять 4 константами: пиками, сердцами, бриллианты и клубы! Программист переменного тока выделил бы 1 байт для каждого: 'const char CLUBS = 0;' и т.д ... Да, поиск HashMap - это O (1), но нагрузка на память и ЦП HashMap в этом случае делает его на много порядков медленнее и требует ресурсов, чем непосредственный вызов .values ​​()! Неудивительно, что Java - такой бред памяти, если люди так пишут ...
Лешек,
2
Не каждая программа требует исполнения тройной игры. Во многих случаях оправдано использование памяти и ЦП для безопасности типов, читабельности, удобства обслуживания, межплатформенной поддержки, сборки мусора и т. Языки более высокого уровня существуют по причине.
января
3
Но если ваш диапазон ключей всегда 0...(n-1), то массив меньше кода и также более читабелен; повышение производительности - это просто бонус. private static final Suit[] VALUES = values();и public Suit fromOrdinal(int ordinal) { return VALUES[ordinal]; }. Дополнительное преимущество: сбой сразу при неверных порядковых номерах, вместо того, чтобы молча вернуть ноль (Не всегда преимущество. Но часто.)
Томас
4

Это то, что я использую. Я не претендую на то, что он гораздо менее «эффективен», чем простые решения выше. Что он делает, так это предоставляет гораздо более четкое сообщение об исключении, чем «ArrayIndexOutOfBounds», когда в решении выше используется недопустимый порядковый номер.

Он использует тот факт, что EnumSet javadoc указывает, что итератор возвращает элементы в их естественном порядке. Есть утверждение, что это не правильно.

Тест JUnit4 демонстрирует, как он используется.

 /**
 * convert ordinal to Enum
 * @param clzz may not be null
 * @param ordinal
 * @return e with e.ordinal( ) == ordinal
 * @throws IllegalArgumentException if ordinal out of range
 */
public static <E extends Enum<E> > E lookupEnum(Class<E> clzz, int ordinal) {
    EnumSet<E> set = EnumSet.allOf(clzz);
    if (ordinal < set.size()) {
        Iterator<E> iter = set.iterator();
        for (int i = 0; i < ordinal; i++) {
            iter.next();
        }
        E rval = iter.next();
        assert(rval.ordinal() == ordinal);
        return rval;
    }
    throw new IllegalArgumentException("Invalid value " + ordinal + " for " + clzz.getName( ) + ", must be < " + set.size());
}

@Test
public void lookupTest( ) {
    java.util.concurrent.TimeUnit tu = lookupEnum(TimeUnit.class, 3);
    System.out.println(tu);
}
gerardw
источник
1

Вот что я делаю на Android с Proguard:

public enum SomeStatus {
    UNINITIALIZED, STATUS_1, RESERVED_1, STATUS_2, RESERVED_2, STATUS_3;//do not change order

    private static SomeStatus[] values = null;
    public static SomeStatus fromInteger(int i) {
        if(SomeStatus.values == null) {
            SomeStatus.values = SomeStatus.values();
        }
        if (i < 0) return SomeStatus.values[0];
        if (i >= SomeStatus.values.length) return SomeStatus.values[0];
        return SomeStatus.values[i];
    }
}

это коротко, и мне не нужно беспокоиться о наличии исключения в Proguard

Кто-то где-то
источник
1

Вы можете определить простой метод как:

public enum Alphabet{
    A,B,C,D;

    public static Alphabet get(int index){
        return Alphabet.values()[index];
    }
}

И используйте это как:

System.out.println(Alphabet.get(2));
Амир Фо
источник
0
public enum Suit implements java.io.Serializable, Comparable<Suit>{
  spades, hearts, diamonds, clubs;
  private static final Suit [] lookup  = Suit.values();
  public Suit fromOrdinal(int ordinal) {
    if(ordinal< 1 || ordinal> 3) return null;
    return lookup[value-1];
  }
}

тестовый класс

public class MainTest {
    public static void main(String[] args) {
        Suit d3 = Suit.diamonds;
        Suit d3Test = Suit.fromOrdinal(2);
        if(d3.equals(d3Test)){
            System.out.println("Susses");
        }else System.out.println("Fails");
    }
}

Я ценю, что вы поделитесь с нами, если у вас есть более эффективный код, Мой список огромен и постоянно вызывается тысячи раз.

Ар маж
источник
Я думаю, что вы имели в виду "если (ординал <1 || ординал> 4) вернуть ноль;"
Geowar
0

Таким образом, один из способов - сделать то, ExampleEnum valueOfOrdinal = ExampleEnum.values()[ordinal];что работает, и это легко, однако, как уже упоминалось, ExampleEnum.values()возвращает новый клонированный массив для каждого вызова. Это может быть излишне дорого. Мы можем решить это, кэшируя массив следующим образом ExampleEnum[] values = values(). Также «опасно» разрешать изменение нашего кэшированного массива. Кто-то может написать ExampleEnum.values[0] = ExampleEnum.type2;Так что я бы сделал это приватным с помощью метода доступа, который не выполняет дополнительное копирование.

private enum ExampleEnum{
    type0, type1, type2, type3;
    private static final ExampleEnum[] values = values();
    public static ExampleEnum value(int ord) {
        return values[ord];
    }
}

Вы бы использовали, ExampleEnum.value(ordinal)чтобы получить значение enum, связанное сordinal

Джо Пеллетье
источник
-1

Каждое перечисление имеет имя (), которое дает строку с именем члена перечисления.

Учитывая enum Suit{Heart, Spade, Club, Diamond}, Suit.Heart.name()даст Heart.

Каждое перечисление имеет valueOf() метод, который принимает тип перечисления и строку для выполнения обратной операции:

Enum.valueOf(Suit.class, "Heart")возвращается Suit.Heart.

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

Тони БенБрахим
источник
2
Потому что сравнение целых чисел намного быстрее, чем сравнение строк?
HighCommander4
2
Но порядковые номера изменятся, если кто-то изменит enum (добавляет / переупорядочивает memvers). Иногда речь идет о безопасности, а не скорости, особенно на странице JSP, где задержка сети в 1000000 раз больше разницы между сравнением массива целых чисел (строки) и одного целого числа.
Тони БенБрахим
toString может быть переопределено, поэтому он не может возвращать имя перечисления. Имя метода () - это то, что дает имя перечисления (оно окончательно)
gerardw
комментарии к коду и версии приложений тоже важны (возможно, формат файла проще и меньше)
Джо
Не уверен, почему это было отвергнуто, когда он по существу рекомендует то же самое, что и в ответе oxbow_lakes. Определенно безопаснее, чем использование порядкового номера.