Преобразование целочисленного значения в соответствие Java Enum

86

У меня такое перечисление:

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);
    private final int value;   

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

Теперь я получаю int от внешнего ввода и хочу соответствующий ввод - выброс исключения, если значение не существует, это нормально, но желательно, чтобы оно было DLT_UNKNOWN в этом случае.

int val = in.readInt();
PcapLinkType type = ???; /*convert val to a PcapLinkType */
Lyke
источник

Ответы:

105

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

private static final Map<Integer, PcapLinkType> intToTypeMap = new HashMap<Integer, PcapLinkType>();
static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.value, type);
    }
}

public static PcapLinkType fromInt(int i) {
    PcapLinkType type = intToTypeMap.get(Integer.valueOf(i));
    if (type == null) 
        return PcapLinkType.DLT_UNKNOWN;
    return type;
}
ЯБольшойЖирныйГай
источник
1
обновлен рекомендациями от dty, что было хорошей идеей.
MeBigFatGuy
Надеюсь, вы сначала пропустили мой код через компилятор ... Я просто придумал это в уме. Я знаю, что методика работает - вчера воспользовалась. Но код находится на другой машине, и на ней нет моих инструментов разработчика.
dty
1
allOf доступен только для наборов
MeBigFatGuy
1
Кроме того, EnumMapв качестве ключей используются перечисления. В этом случае OP хочет, чтобы перечисления были значениями.
dty
8
Это похоже на много ненужных накладных расходов. Те, кому действительно нужен этот тип операции, вероятно, нуждаются в высокой производительности, потому что они пишут / читают из потоков / сокетов, и в этом случае кеширование values()(если ваши значения перечисления последовательны) или простой switchоператор легко превзойдет этот метод . Если у вас есть только несколько записей в вашем, Enumтогда не имеет большого смысла добавлять накладные расходы на HashMap просто для удобства, чтобы не обновлять switchоператор. Этот метод может показаться более элегантным, но он также расточителен.
раздавить
30

Там есть статический метод , values()который является документально, но не там , где вы ожидаете: http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

enum MyEnum {
    FIRST, SECOND, THIRD;
    private static MyEnum[] allValues = values();
    public static MyEnum fromOrdinal(int n) {return allValues[n];}
}

В принципе, вы можете использовать только values()[i], но ходят слухи, что values()каждый раз будет создавать копию массива при его вызове.

18446744073709551615
источник
9
Согласно Джошуа Блоху (Эффективная книга по Java) : Никогда не извлекайте значение, связанное с перечислением, из его порядкового номера; Ваша реализация не должна полагаться на порядок перечислений.
stevo.mit
4
Реализация чего? Если мы реализуем какой-либо алгоритм, реализация не должна полагаться на порядок перечислений, если этот порядок не задокументирован. Когда мы реализуем само перечисление, можно использовать такие детали реализации точно так же, как можно использовать частные методы класса.
18446744073709551615
1
Не согласен. Я считаю, что никогда не подразумевается независимо от документации. Вам не следует использовать порядковые числа, даже если вы сами реализуете перечисление. Это неприятный запах и подвержен ошибкам. Я не эксперт, но я бы не стал спорить с Джошуа Блохом :)
stevo.mit
4
@ stevo.mit взгляните на новый enum java.time.Month в Java 8. Статический метод Month.of (int) делает именно то, что, по словам Джошуа Блоха, «никогда» делать нельзя. Он возвращает месяц в зависимости от его порядкового номера.
Klitos Kyriacou
1
@ stevo.mit Есть упорядоченные перечисления и неупорядоченные перечисления . (И перечисления битовых масок тоже.) Говорить о них просто как о «перечислениях» просто некорректно. Решение о том, какие выразительные средства использовать, должно основываться на уровне абстракции, над которым вы работаете. Действительно, неправильно использовать детали реализации (выразительные средства более низкого уровня) или предположения об использовании (выразительные средства более высокого уровня). Что касается « никогда », то в человеческих языках никогда не означает никогда, потому что всегда есть какой-то контекст. (Не Обычно, при программировании приложений, никогда ...) Кстати, programering.com/a/MzNxQjMwATM.html
18446744073709551615
14

Вам нужно будет создать новый статический метод, в котором вы повторяете PcapLinkType.values ​​() и сравниваете:

public static PcapLinkType forCode(int code) {
    for (PcapLinkType typе : PcapLinkType.values()) {
        if (type.getValue() == code) {
            return type;
        }
    }
    return null;
 }

Было бы хорошо, если бы он назывался редко. Если он вызывается часто, посмотрите на Mapоптимизацию, предложенную другими.

Божо
источник
4
Может быть дорого, если звонить много. Создание статической карты, вероятно, даст лучшую амортизируемую стоимость.
dty
@dty o (n) с n = 200 - я не думаю, что это проблема
Bozho
7
Это совершенно нелепое заявление, даже если не знать, как часто оно вызывается. Если он вызван один раз, хорошо. Если он вызывается для каждого пакета, проходящего мимо в сети 10Ge, очень важно сделать алгоритм в 200 раз быстрее. Следовательно, почему я квалифицировал свое утверждение как «если много
звонили
10

Вы можете сделать что-то подобное, чтобы автоматически зарегистрировать их все в коллекции, с помощью которой затем легко преобразовать целые числа в соответствующее перечисление. (Кстати, добавление их на карту в конструкторе enum запрещено . Приятно узнавать что-то новое даже после многих лет использования Java. :)

public enum PcapLinkType {
    DLT_NULL(0),
    DLT_EN10MB(1),
    DLT_EN3MB(2),
    DLT_AX25(3),
    /*snip, 200 more enums, not always consecutive.*/
    DLT_UNKNOWN(-1);

    private static final Map<Integer, PcapLinkType> typesByValue = new HashMap<Integer, PcapLinkType>();

    static {
        for (PcapLinkType type : PcapLinkType.values()) {
            typesByValue.put(type.value, type);
        }
    }

    private final int value;

    private PcapLinkType(int value) {
        this.value = value;
    }

    public static PcapLinkType forValue(int value) {
        return typesByValue.get(value);
    }
}
Эско Луонтола
источник
1
Это то, что вы получаете, дважды проверяя свой ответ перед публикацией. ;)
Эско Луонтола
10

если у вас есть такое перечисление

public enum PcapLinkType {
  DLT_NULL(0)
  DLT_EN10MB(1)
  DLT_EN3MB(2),
  DLT_AX25(3),
  DLT_UNKNOWN(-1);

    private final int value;   

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

тогда вы можете использовать это как

PcapLinkType type = PcapLinkType.values()[1]; /*convert val to a PcapLinkType */
Джек Гаджанан
источник
вы пропустили комментарий / * snip, еще 200 перечислений, не всегда последовательных. * /
MeBigFatGuy
на всякий случай, если ваше значение enum является транзитивным от нуля, это плохая практика
cuasodayleo 09
4

Как говорит @MeBigFatGuy, за исключением того, что вы можете заставить свой static {...}блок использовать цикл над values()коллекцией:

static {
    for (PcapLinkType type : PcapLinkType.values()) {
        intToTypeMap.put(type.getValue(), type);
    }
}
dty
источник
4

Я знаю, что этому вопросу несколько лет, но, поскольку Java 8 тем временем принесла нам Optional, я подумал, что предлагаю решение, использующее его (и Streamи Collectors):

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  // DLT_UNKNOWN(-1); // <--- NO LONGER NEEDED

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

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static Optional<PcapLinkType> fromInt(int value) {
    return Optional.ofNullable(map.get(value));
  }
}

Optionalпохоже null: он представляет собой случай, когда нет (действительного) значения. Но это более безопасная для типов альтернатива nullили значение по умолчанию, например, DLT_UNKNOWNпотому что вы можете забыть проверить регистры nullили DLT_UNKNOWN. Оба PcapLinkTypeзначения допустимы ! Напротив, вы не можете присвоить Optional<PcapLinkType>значение переменной типа PcapLinkType. Optionalзаставляет вас сначала проверять допустимое значение.

Конечно, если вы хотите сохранить DLT_UNKNOWNдля обратной совместимости или по любой другой причине, вы все равно можете использовать Optionalдаже в этом случае, используя orElse()для указания его в качестве значения по умолчанию:

public enum PcapLinkType {
  DLT_NULL(0),
  DLT_EN3MB(2),
  DLT_AX25(3),
  /*snip, 200 more enums, not always consecutive.*/
  DLT_UNKNOWN(-1);

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

  private static final Map<Integer, PcapLinkType> map;
  static {
    map = Arrays.stream(values())
        .collect(Collectors.toMap(e -> e.value, e -> e));
  }

  public static PcapLinkType fromInt(int value) {
    return Optional.ofNullable(map.get(value)).orElse(DLT_UNKNOWN);
  }
}
Брэд Коллинз
источник
3

Вы можете добавить статический метод в свое перечисление, которое принимает intв качестве параметра и возвращает PcapLinkType.

public static PcapLinkType of(int linkType) {

    switch (linkType) {
        case -1: return DLT_UNKNOWN
        case 0: return DLT_NULL;

        //ETC....

        default: return null;

    }
}
Бухаке Синди
источник
Лучше не забудьте добавить запись в этот switchоператор, если вы добавите новое перечисление. Не идеальный, ИМХО.
dty
1
@dty Итак, вы думаете, что накладные расходы на HashMap перевешивают необходимость добавлять новый регистр в оператор switch?
Crush
1
Я думаю, что лучше напишу код, который помогает мне не делать ошибок и, следовательно, с большей вероятностью будет правильным, прежде чем я сосредоточусь на микропроизводительности хеш-поиска.
dty 06
3

Вот что я использую:

public enum Quality {ENOUGH,BETTER,BEST;
                     private static final int amount = EnumSet.allOf(Quality.class).size();
                     private static Quality[] val = new Quality[amount];
                     static{ for(Quality q:EnumSet.allOf(Quality.class)){ val[q.ordinal()]=q; } }
                     public static Quality fromInt(int i) { return val[i]; }
                     public Quality next() { return fromInt((ordinal()+1)%amount); }
                    }
18446744073709551615
источник
Использование порядкового номера считается плохой практикой, в общем, лучше избегать.
Рафаэль
1
static final PcapLinkType[] values  = { DLT_NULL, DLT_EN10MB, DLT_EN3MB, null ...}    

...

public static PcapLinkType  getPcapLinkTypeForInt(int num){    
    try{    
       return values[int];    
    }catch(ArrayIndexOutOfBoundsException e){    
       return DLT_UKNOWN;    
    }    
}    
nsfyn55
источник
1
Дорого, если звонить много. Необходимо помнить об обновлении массива (почему он у вас вообще есть, когда перечисления определяют .values()метод?).
dty
@dty это попытка / уловка? Я думаю, было бы справедливее сказать, что это дорого, если бы многие значения попадали в категорию DLT_UNKNOWN.
nsfyn55
1
Я действительно удивлен, увидев, что решение для массива отвергнуто, а решение для карты проголосовано за. Что мне здесь не нравится, так это --int, но это явно опечатка.
18446744073709551615
Вижу: хотят nullвместо DLT_UKNOWN:)
18446744073709551615 02
1
А почему бы и нет static final values[] = PcapLinkType.values()?
18446744073709551615 02
0

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

public enum Port {
  /**
   * The default port for the push server.
   */
  DEFAULT("443"),

  /**
   * The alternative port that can be used to bypass firewall checks
   * made to the default <i>HTTPS</i> port.
   */
  ALTERNATIVE("2197");

  private final String portString;

  Port(final String portString) {
    this.portString = portString;
  }

  /**
   * Returns the port for given {@link Port} enumeration value.
   * @return The port of the push server host.
   */
  public Integer toInteger() {
    return Integer.parseInt(portString);
  }
}
Бугра Экуклу
источник