Как я могу найти перечисление Java по его строковому значению?

164

Я хотел бы найти перечисление из его строкового значения (или, возможно, любого другого значения). Я пробовал следующий код, но он не позволяет статический в инициализаторах. Есть ли простой способ?

public enum Verbosity {

    BRIEF, NORMAL, FULL;

    private static Map<String, Verbosity> stringMap = new HashMap<String, Verbosity>();

    private Verbosity() {
        stringMap.put(this.toString(), this);
    }

    public static Verbosity getVerbosity(String key) {
        return stringMap.get(key);
    }
};
peter.murray.rust
источник
IIRC, что дает NPE, потому что статическая инициализация выполняется сверху вниз (то есть константы перечисления вверху создаются до того, как он переходит к stringMapинициализации). Обычное решение - использовать вложенный класс.
Том Хотин - Tackline
Спасибо всем за такой быстрый ответ. (FWIW Я не нашел Sun Javadocs очень полезным для этой проблемы).
peter.murray.rust
Это действительно проблема языка, а не проблема библиотеки. Тем не менее, я думаю, что документы API читаются больше, чем JLS (хотя, возможно, не разработчиками языка), поэтому такие вещи, вероятно, должны иметь большее значение в документах java.lang.
Том Хотин - Tackline

Ответы:

241

Используйте valueOfметод, который автоматически создается для каждого Enum.

Verbosity.valueOf("BRIEF") == Verbosity.BRIEF

Для произвольных значений начните с:

public static Verbosity findByAbbr(String abbr){
    for(Verbosity v : values()){
        if( v.abbr().equals(abbr)){
            return v;
        }
    }
    return null;
}

Перейдите к реализации Map только позже, если ваш профилировщик скажет вам об этом.

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

Гарет Дэвис
источник
спасибо - и я могу использовать преобразование регистра, если я знаю, что значения все еще различны
peter.murray.rust
Вы можете напрямую получить доступ к члену класса 'abbr', поэтому вместо "v.abbr ()" вы можете использовать "v.abbr.equals ...".
Amio.io
7
Кроме того, для произвольных значений (во втором случае) рассмотрите выбрасывание IllegalArgumentExceptionвместо того, чтобы возвращать ноль ( то есть, когда совпадение не найдено) - это то, как JDK Enum.valueOf(..)делает это, в любом случае.
Прииду Нимре
135

Ты рядом. Для произвольных значений попробуйте что-то вроде следующего:

public enum Day { 

    MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
    THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

    private final String abbreviation;

    // Reverse-lookup map for getting a day from an abbreviation
    private static final Map<String, Day> lookup = new HashMap<String, Day>();

    static {
        for (Day d : Day.values()) {
            lookup.put(d.getAbbreviation(), d);
        }
    }

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day get(String abbreviation) {
        return lookup.get(abbreviation);
    }
}
Lyle
источник
4
Из-за проблем с загрузчиком классов этот метод не будет работать надежно. Я не рекомендую это, и я видел, что это регулярно терпело неудачу, потому что карта поиска не готова перед доступом. Вам нужно поместить "lookup" вне enum или другого класса, чтобы он загружался первым.
Адам Гент
7
@ Adam Gent: внизу есть ссылка на JLS, где точная конструкция представлена ​​в качестве примера. В нем говорится, что статическая инициализация происходит сверху вниз: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#d5e12267
Селена,
3
Да, современная Java, как Java 8, исправила модель памяти. Это означает, что агенты горячей замены, такие как jrebel, все равно будут испорчены, если вы добавите инициализацию из-за циклических ссылок.
Адам Гент
@ Адам Гент: Хммм. Я узнаю что-то новое каждый день. [Тихо крадется, чтобы рефакторировать мои перечисления с помощью синглетонов начальной загрузки ...] Извините, если это глупый вопрос, но в основном ли это проблема со статической инициализацией?
Селена
Я никогда не видел проблем с тем, чтобы статический блок кода не инициализировался, но я использовал Java 8 только с тех пор, как начал работать на Java в 2015 году. Я просто провел небольшой тест, используя JMH 1.22 и используя a HashMap<>для хранения перечислений, которые оказались более чем на 40% быстрее, чем петля.
cbaldan
21

с Java 8 вы можете достичь с помощью этого способа:

public static Verbosity findByAbbr(final String abbr){
    return Arrays.stream(values()).filter(value -> value.abbr().equals(abbr)).findFirst().orElse(null);
}
Лам Ле
источник
Я бы бросил исключение, а не возвращался null. Но это также зависит от случая использования
АЛЕКСАНДР
12

@ Ответ Лайла довольно опасен, и я видел, что он не работает, особенно если сделать enum статическим внутренним классом. Вместо этого я использовал что-то вроде этого, которое будет загружать карты BootstrapSingleton до перечисления.

Редактировать это больше не должно быть проблемой с современными JVM (JVM 1.6 или выше), но я думаю, что с JRebel все еще есть проблемы, но у меня не было возможности повторно протестировать его .

Загрузи меня первым:

   public final class BootstrapSingleton {

        // Reverse-lookup map for getting a day from an abbreviation
        public static final Map<String, Day> lookup = new HashMap<String, Day>();
   }

Теперь загрузите его в конструктор enum:

   public enum Day { 
        MONDAY("M"), TUESDAY("T"), WEDNESDAY("W"),
        THURSDAY("R"), FRIDAY("F"), SATURDAY("Sa"), SUNDAY("Su"), ;

        private final String abbreviation;

        private Day(String abbreviation) {
            this.abbreviation = abbreviation;
            BootstrapSingleton.lookup.put(abbreviation, this);
        }

        public String getAbbreviation() {
            return abbreviation;
        }

        public static Day get(String abbreviation) {
            return lookup.get(abbreviation);
        }
    }

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

Адам Гент
источник
3
Необходимо определить «опасный» и почему имеет значение реализация внутреннего класса. Это скорее проблема статического определения сверху вниз, но в другом связанном ответе есть рабочий код со ссылкой на этот вопрос, который использует статическую карту на самом экземпляре Enum с методом «get» из пользовательского значения для Тип enum. stackoverflow.com/questions/604424/lookup-enum-by-string-value
Даррелл Тиг
2
@DarrellTeague Первоначальная проблема была связана со старыми JVM (до 1.6) или другими JVM (IBM) или подменщиками горячих кодов (JRebel). Эта проблема не должна возникать в современных JVM, поэтому я могу просто удалить свой ответ.
Адам Гент
Приятно отметить, что действительно это возможно благодаря пользовательским реализациям компилятора и оптимизации времени выполнения ... упорядочение может быть переупорядочено, что приведет к ошибке. Тем не менее, это может привести к нарушению спецификации.
Даррелл Тигу
7

И вы не можете использовать valueOf () ?

Редактировать: Кстати, ничто не мешает вам использовать static {} в перечислении.

Фредрик
источник
Или синтетика valueOfдля класса enum, поэтому вам не нужно указывать класс enum Class.
Том Хотин - Tackline
Конечно, у него просто не было записи Javadoc, поэтому было трудно на него ссылаться.
Фредрик
1
Вы можете использовать valueOf (), но имя должно совпадать с тем, которое задано в объявлении enum. Любой из двух указанных выше методов поиска может быть изменен для использования .equalsIgnoringCase () и имеет немного большую устойчивость к ошибкам.
@leonardo Правда. Если это добавляет устойчивости или просто отказоустойчивость спорно. В большинстве случаев я бы сказал, что это последнее, и в этом случае лучше обрабатывать его в другом месте и по-прежнему использовать Enum valueOf ().
Фредрик
4

В случае, если это помогает другим, предпочтительный вариант, которого здесь нет в списке, использует функциональность карт Guava :

public enum Vebosity {
    BRIEF("BRIEF"),
    NORMAL("NORMAL"),
    FULL("FULL");

    private String value;
    private Verbosity(final String value) {
        this.value = value;
    }

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

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

    public static Verbosity fromString(final String id) {
        return reverseLookup.getOrDefault(id, NORMAL);
    }
}

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

Чад Бефус
источник
3

начиная с Java 8 вы можете инициализировать карту в одну строку и без статического блока

private static Map<String, Verbosity> stringMap = Arrays.stream(values())
                 .collect(Collectors.toMap(Enum::toString, Function.identity()));
Адриан
источник
+1 Отличное использование потоков, и очень сжатый, не жертвуя читаемостью! Я также не видел такого использования Function.identity()раньше, я нахожу его гораздо более привлекательным с точки зрения текста, чем e -> e.
Мацу В.
0

Возможно, взгляните на это. Это работает для меня. Целью этого является поиск «RED» с «/ red_color». Объявление a static mapи загрузка enums в него только один раз принесут некоторые преимущества в производительности, если enums много.

public class Mapper {

public enum Maps {

    COLOR_RED("/red_color", "RED");

    private final String code;
    private final String description;
    private static Map<String, String> mMap;

    private Maps(String code, String description) {
        this.code = code;
        this.description = description;
    }

    public String getCode() {
        return name();
    }

    public String getDescription() {
        return description;
    }

    public String getName() {
        return name();
    }

    public static String getColorName(String uri) {
        if (mMap == null) {
            initializeMapping();
        }
        if (mMap.containsKey(uri)) {
            return mMap.get(uri);
        }
        return null;
    }

    private static void initializeMapping() {
        mMap = new HashMap<String, String>();
        for (Maps s : Maps.values()) {
            mMap.put(s.code, s.description);
        }
    }
}
}

Пожалуйста, оставьте свой отзыв.

Midhun
источник
-1

Вы можете определить свой Enum как следующий код:

public enum Verbosity 
{
   BRIEF, NORMAL, FULL, ACTION_NOT_VALID;
   private int value;

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

   public static final Verbosity getVerbosityByValue(int value)
   {
     for(Verbosity verbosity : Verbosity.values())
     {
        if(verbosity.getValue() == value)
            return verbosity ;
     }

     return ACTION_NOT_VALID;
   }

   @Override
   public String toString()
   {
      return ((Integer)this.getValue()).toString();
   }
};

Смотрите следующую ссылку для получения дополнительной информации

Винай Шарма
источник
-1

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

    public static final Verbosity lookup(String name) {
        return lookup(name, null);
    }

    public static final Verbosity lookup(String name, Verbosity dflt) {
        if (StringUtils.isBlank(name)) {
            return dflt;
        }
        if (name.matches("^\\d.*")) {
            name = "_"+name;
        }
        try {
            return Verbosity.valueOf(name);
        } catch (IllegalArgumentException e) {
            return dflt;
        }
    }

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

ScrappyDev
источник
-1
public enum EnumRole {

ROLE_ANONYMOUS_USER_ROLE ("anonymous user role"),
ROLE_INTERNAL ("internal role");

private String roleName;

public String getRoleName() {
    return roleName;
}

EnumRole(String roleName) {
    this.roleName = roleName;
}

public static final EnumRole getByValue(String value){
    return Arrays.stream(EnumRole.values()).filter(enumRole -> enumRole.roleName.equals(value)).findFirst().orElse(ROLE_ANONYMOUS_USER_ROLE);
}

public static void main(String[] args) {
    System.out.println(getByValue("internal role").roleName);
}

}

Вахап Генчдал
источник
-2

Вы можете использовать Enum::valueOf()функцию, предложенную Gareth Davis & Brad Mace выше, но убедитесь, что вы обрабатываете то, IllegalArgumentExceptionчто было бы выброшено, если используемая строка отсутствует в перечислении.

Маянк Бутпори
источник