Проверьте, существует ли перечисление в Java

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


person Danny    schedule 22.07.2009    source источник
comment
Я действительно не понимаю идеи emum valueOf создать исключение ... это не имеет никакого смысла. Было бы намного практичнее во всех аспектах, если бы он просто возвращал NULL.   -  person marcolopes    schedule 06.02.2013
comment
@marcolopes: Одна из причин - желание охватить все возможные случаи с помощью Enum. Если Enum не найден, это означает, что разработчик должен быть как можно скорее уведомлен о пропущенном кейсе. Он не должен позволять программе генерировать ошибку NullPointerError где-то еще, позже в коде.   -  person Eric Duminil    schedule 07.02.2019
comment
@EricDuminil с нулевым результатом было бы проще ... вот что я делаю! Я в основном не использую valueOf в своем коде и пишу новый метод внутри enum get (value), который перехватывает исключение ...   -  person marcolopes    schedule 11.02.2019


Ответы (9)


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

public static MyEnum asMyEnum(String str) {
    for (MyEnum me : MyEnum.values()) {
        if (me.name().equalsIgnoreCase(str))
            return me;
    }
    return null;
}

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

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

person Michael Myers    schedule 22.07.2009

Если мне нужно это сделать, я иногда создаю Set<String> имен или даже свое Map<String,MyEnum> - тогда вы можете просто проверить это.

Стоит отметить несколько моментов:

  • Заполните любую такую ​​статическую коллекцию в статическом инициализаторе. Не используйте инициализатор переменной, а затем полагайтесь на его выполнение при запуске конструктора перечисления - этого не было! (Конструкторы перечисления - это первое, что нужно выполнить перед статическим инициализатором.)
  • Старайтесь избегать частого использования values() - ему каждый раз приходится создавать и заполнять новый массив. Чтобы перебрать все элементы, используйте _ 4_, что намного эффективнее для перечислений без большого количества элементов.

Образец кода:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    private static final Map<String, SampleEnum> nameToValueMap =
        new HashMap<String, SampleEnum>();
    
    static {
        for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }
    
    public static SampleEnum forName(String name) {
        return nameToValueMap.get(name);
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}

Конечно, если у вас всего несколько имен, это, вероятно, излишество - решение O (n) часто побеждает решение O (1), когда n достаточно мало. Вот еще один подход:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    // We know we'll never mutate this, so we can keep
    // a local copy.
    private static final SampleEnum[] copyOfValues = values();
    
    public static SampleEnum forName(String name) {
        for (SampleEnum value : copyOfValues) {
            if (value.name().equals(name)) {
                return value;
            }
        }
        return null;
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}
person Jon Skeet    schedule 22.07.2009
comment
values() создает и заполняет новый массив? Я не помню, чтобы слышал это раньше, но, возможно, у вас есть источник? - person Michael Myers; 23.07.2009
comment
Ну, он возвращает массив ... и не может защитить вас от изменения этого массива ... и это не помешает последующим вызывающим объектам. Кроме этого, посмотрите исходники JRE :) - person Jon Skeet; 23.07.2009
comment
Этого нет в источниках JRE, поэтому я и спросил. Но я не думал об аспекте мутации; Вы, наверное, правы. - person Michael Myers; 23.07.2009
comment
Упс, извините - да, только что проверил. Затем декомпилируйте перечисление :) Он использует clone (), который может быть довольно быстрым, но не таким быстрым, как отсутствие необходимости делать это вообще ... - person Jon Skeet; 23.07.2009
comment
Ах, вопрос о том, как работает values(), был задан, и на него ответили в течение последнего дня: stackoverflow.com/questions/1163076/. - person Michael Myers; 23.07.2009
comment
Бьюсь об заклад, стоимость клона минимальна для проверки через массив (особенно, если цель String не интернирована.) - person Tom Hawtin - tackline; 23.07.2009
comment
Существуют ли какие-либо рекомендации относительно того, какие размеры перечисления выигрывают от подхода на основе карты по сравнению с подходом на основе массива / цикла? (Я не ищу жестких / быстрых правил, просто примерный пример того, когда следует думать о том, чтобы действовать так, а не другим) - person cdeszaq; 12.05.2014
comment
@cdeszaq: я подозреваю, что это может значительно различаться между JVM и ситуациями. Если что-то, что вас беспокоит, может иметь важное значение в вашем приложении, я предлагаю вам провести тесты производительности. Извините, что не могу быть более полезным :( - person Jon Skeet; 12.05.2014

Одна из моих любимых библиотек: Apache Commons.

EnumUtils легко это сделать.

Следуя примеру для проверки Enum с этой библиотекой:

public enum MyEnum {
    DIV("div"), DEPT("dept"), CLASS("class");

    private final String val;

    MyEnum(String val) {
    this.val = val;
    }

    public String getVal() {
    return val;
    }
}


MyEnum strTypeEnum = null;

// test if String str is compatible with the enum 
// e.g. if you pass str = "div", it will return false. If you pass "DIV", it will return true.
if( EnumUtils.isValidEnum(MyEnum.class, str) ){
    strTypeEnum = MyEnum.valueOf(str);
}
person рüффп    schedule 01.03.2013
comment
Спасибо за ваш комментарий. На самом деле это минимально, потому что использование библиотек, которые использовали другие, часто является лучшим решением, чем перекодирование самостоятельно. Если вы делаете это самостоятельно, вы приходите к тому же самому решению (изобретая колесо заново), или вы упускаете что-то важное, и это ведет себя хуже всего (в этом случае вы заново изобретаете квадратное колесо) - person рüффп; 07.01.2017
comment
Да, точно. Apache Commons - это полностью оборудованная кухня; стоит только тогда, когда вы получаете от этого полную или максимальную выгоду :) Квадратное колесо: P - person mumair; 07.01.2017
comment
На мой взгляд, Apache Commons должны быть общими базовыми зависимостями для добавления в любые проекты Java;) - person рüффп; 07.01.2017
comment
к сожалению, я использую commons-lang-2.6.jar, я не нашел EnumUtils.isValidEnum, что делать дальше? - person Dasari Vinodh; 31.08.2018
comment
@DasariVinodh, может, пора перенести зависимости? - person рüффп; 18.09.2018
comment
Эта реализация действительно пытается уловить valueOf () внутренне, что именно то, чего OP пытается избежать. Я не знаю, как этот ответ получил столько голосов. - person harogaston; 16.08.2019
comment
@harogaston: есть разница между перехватом исключений самостоятельно или делегированием их библиотеке. OP вообще не упоминал о запрете обработки исключений. Библиотека предполагается оптимизировать и находится под контролем. По крайней мере, это делает ваш код более чистым и не управляет исключением самостоятельно (и подвержено потенциальным ошибкам, так я понял вопрос). Реализация библиотеки может использовать любые методы, и даже она может меняться со временем, ваш код не будет отличаться. - person рüффп; 11.09.2019
comment
@ рüффп OP ясно говорит, но меня учили, что перехват исключений времени выполнения - не лучшая практика. Эта реализация делает именно это. Кроме того, нет способа оптимизировать перехват исключения, если вы не настроите саму JVM. Так что это остается неправильным ответом. - person harogaston; 11.09.2019
comment
Поймать исключение времени выполнения не всегда является плохой практикой, это следует делать с осторожностью в отношении определенных исключений. Большинство наиболее часто используемых фреймворков (например, Camel, Spring и т. Д.) Делают это, и программисты продолжают использовать их, не задумываясь о том, является ли это хорошей практикой или нет. Перехват Throwable или корневого исключения определенно не является хорошей практикой, но у меня есть несколько случаев, когда сторонняя сторона выдает такое исключение, и у меня нет выбора, кроме как перехватить исключение класса в моем коде. Посмотрите этот ответ. - person рüффп; 12.12.2019

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

Использование valueOf и перехват IllegalArgumentException отлично подходит для преобразования / проверки строки в перечисление.

person nos    schedule 22.07.2009
comment
Нет, ИМО. Это проверка неисключительной ситуации с помощью исключений - использование их для управления потоком в нормальных условиях, не связанных с ошибками. Это очень плохое использование исключений IMO, которое может иметь значительное влияние на производительность. Обычно исключения хороши с точки зрения производительности, потому что они не должны происходить, но когда вы используете их для условий, не связанных с ошибками, тогда код, который выглядит так, как будто он должен выполняться быстро, может застрять. - person Jon Skeet; 23.07.2009
comment
Забавно - основываясь на этом мнении (которое я обычно разделяю), я решил использовать Apache Commons EnumUtils.isValidEnum, чтобы сделать его как можно более чистым. И угадайте, как EnumUtils.isValidEnum реализовано - ловить, конечно, IllegalArgumentException :-) - person Michal Aron; 25.11.2015
comment
Что ж, если строка, которую вы тестируете, предоставлена ​​пользователем, то ожидаемая ситуация получить исключение IllegalArgumentException, верно? Честно говоря, я немного удивлен, что в Enum нет метода для этого варианта использования. - person Johanneke; 06.07.2017
comment
@JonSkeet Я думаю, вы ошибаетесь. Здесь речь не идет об управлении потоком, давайте возьмем API, часто мы, возможно, будем ждать строковое значение от api (часто определяется приложением и теоретически имеет 100% шанс существования, давайте поговорим об отправленном заказе, ожидающем отмены), поэтому, если мой api принимает только 3 значения в качестве перечисления для примера, и мы предоставляем неправильное значение, оно должно вызывать исключение, которое, вероятно, вызовет неправильный ответ на запрос. - person amdev; 29.08.2019
comment
@amdev: Вам очень легко это обнаружить и выбросить исключение в вашем собственном коде, если вы этого хотите. Для других вариантов использования гораздо неприятнее принимать исключение, когда значение отсутствует. Считаете ли вы, что map.get должен генерировать исключение, если ключ тоже отсутствует? Потенциально было бы чище иметь два отдельных метода - один бросающий, а другой нет - но, хотя есть только один метод, я думаю, что гораздо чище, если бы он не бросал. - person Jon Skeet; 29.08.2019
comment
@amdev Оставляя в стороне соображения стиля, проблема с исключениями заключается в том, что JVM должна подготовить для вас всю трассировку стека, а это занимает некоторое время. Если вы ожидаете много плохих запросов, возможно, стоит реализовать какой-нибудь метод проверки O (1), чтобы избежать накладных расходов, вызванных исключениями. - person DGoiko; 20.02.2020
comment
Конечно, исключение является исключительным, и в моем сценарии приложение отправляет order status, и теоретически перечисление всегда будет соответствовать строке. Все зависит от варианта использования, если вы никогда не исключаете, что перечисление не будет соответствовать строке, поэтому оно является исключительным и должно вызывать исключение. Если это общий случай, да, вам, вероятно, следует просто проверить правильность вашей строки, прежде чем пытаться ее преобразовать, в любом случае в этом сценарии, если вы проверяете свою строку перед вызовом valueOf, все еще очень исключительным является то, что перечисление не будет соответствовать. - person amdev; 05.03.2020

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

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * This permits to easily implement a failsafe implementation of the enums's valueOf
 * Better use it inside the enum so that only one of this object instance exist for each enum...
 * (a cache could solve this if needed)
 * </p>
 *
 * <p>
 * Basic usage exemple on an enum class called MyEnum:
 *
 *   private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
 *   public static MyEnum failSafeValueOf(String enumName) {
 *       return FAIL_SAFE.valueOf(enumName);
 *   }
 *
 * </p>
 *
 * <p>
 * You can also use it outside of the enum this way:
 *   FailSafeValueOf.create(MyEnum.class).valueOf("EnumName");
 * </p>
 *
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOf<T extends Enum<T>> {

    private final Map<String,T> nameToEnumMap;

    private FailSafeValueOf(Class<T> enumClass) {
        Map<String,T> map = Maps.newHashMap();
        for ( T value : EnumSet.allOf(enumClass)) {
            map.put( value.name() , value);
        }
        nameToEnumMap = ImmutableMap.copyOf(map);
    }

    /**
     * Returns the value of the given enum element
     * If the 
     * @param enumName
     * @return
     */
    public T valueOf(String enumName) {
        return nameToEnumMap.get(enumName);
    }

    public static <U extends Enum<U>> FailSafeValueOf<U> create(Class<U> enumClass) {
        return new FailSafeValueOf<U>(enumClass);
    }

}

И модульный тест:

import org.testng.annotations.Test;

import static org.testng.Assert.*;


/**
 * @author Sebastien Lorber <i>([email protected])</i>
 */
public class FailSafeValueOfTest {

    private enum MyEnum {
        TOTO,
        TATA,
        ;

        private static final FailSafeValueOf<MyEnum> FAIL_SAFE = FailSafeValueOf.create(MyEnum.class);
        public static MyEnum failSafeValueOf(String enumName) {
            return FAIL_SAFE.valueOf(enumName);
        }
    }

    @Test
    public void testInEnum() {
        assertNotNull( MyEnum.failSafeValueOf("TOTO") );
        assertNotNull( MyEnum.failSafeValueOf("TATA") );
        assertNull( MyEnum.failSafeValueOf("TITI") );
    }

    @Test
    public void testInApp() {
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TOTO") );
        assertNotNull( FailSafeValueOf.create(MyEnum.class).valueOf("TATA") );
        assertNull( FailSafeValueOf.create(MyEnum.class).valueOf("TITI") );
    }

}

Обратите внимание, что я использовал Guava для создания ImmutableMap, но на самом деле вы могли бы использовать карту нормалей, я думаю, поскольку карта никогда не возвращается ...

person Sebastien Lorber    schedule 23.02.2012

В большинстве ответов предлагается либо использовать цикл с equals, чтобы проверить, существует ли перечисление, либо использовать try / catch с enum.valueOf (). Я хотел узнать, какой метод быстрее, и попробовал. Я не очень хорошо разбираюсь в тестах, поэтому, пожалуйста, поправьте меня, если я допустил какие-либо ошибки.

Вот код моего основного класса:

    package enumtest;

public class TestMain {

    static long timeCatch, timeIterate;
    static String checkFor;
    static int corrects;

    public static void main(String[] args) {
        timeCatch = 0;
        timeIterate = 0;
        TestingEnum[] enumVals = TestingEnum.values();
        String[] testingStrings = new String[enumVals.length * 5];
        for (int j = 0; j < 10000; j++) {
            for (int i = 0; i < testingStrings.length; i++) {
                if (i % 5 == 0) {
                    testingStrings[i] = enumVals[i / 5].toString();
                } else {
                    testingStrings[i] = "DOES_NOT_EXIST" + i;
                }
            }

            for (String s : testingStrings) {
                checkFor = s;
                if (tryCatch()) {
                    ++corrects;
                }
                if (iterate()) {
                    ++corrects;
                }
            }
        }

        System.out.println(timeCatch / 1000 + "us for try catch");
        System.out.println(timeIterate / 1000 + "us for iterate");
        System.out.println(corrects);
    }

    static boolean tryCatch() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        try {
            TestingEnum.valueOf(checkFor);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        } finally {
            timeEnd = System.nanoTime();
            timeCatch += timeEnd - timeStart;
        }

    }

    static boolean iterate() {
        long timeStart, timeEnd;
        timeStart = System.nanoTime();
        TestingEnum[] values = TestingEnum.values();
        for (TestingEnum v : values) {
            if (v.toString().equals(checkFor)) {
                timeEnd = System.nanoTime();
                timeIterate += timeEnd - timeStart;
                return true;
            }
        }
        timeEnd = System.nanoTime();
        timeIterate += timeEnd - timeStart;
        return false;
    }
}

Это означает, что каждый метод выполняется в 50000 раз длиннее перечисления, которое я запускал этот тест несколько раз, с константами перечисления 10, 20, 50 и 100. Вот результаты:

  • 10: попытка / улов: 760 мс | итерация: 62 мс
  • 20: попытка / улов: 1671 мс | итерация: 177 мс
  • 50: попытка / улов: 3113 мс | итерация: 488 мс
  • 100: попытка / улов: 6834 мс | итерация: 1760 мс

Эти результаты не были точными. При его повторном выполнении разница в результатах составляет до 10%, но их достаточно, чтобы показать, что метод try / catch намного менее эффективен, особенно с небольшими перечислениями.

person Alexander Daum    schedule 08.02.2017

Начиная с Java 8, мы могли использовать потоки вместо циклов for. Кроме того, может оказаться целесообразным возвращать Необязательно, если в перечислении нет экземпляра с таким именем.

Я придумал следующие три альтернативы тому, как искать перечисление:

private enum Test {
    TEST1, TEST2;

    public Test fromNameOrThrowException(String name) {
        return Arrays.stream(values())
                .filter(e -> e.name().equals(name))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("No enum with name " + name));
    }

    public Test fromNameOrNull(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst().orElse(null);
    }

    public Optional<Test> fromName(String name) {
        return Arrays.stream(values()).filter(e -> e.name().equals(name)).findFirst();
    }
}
person Magnilex    schedule 05.06.2018

Просто используйте метод valueOf (). Если значение не существует, оно генерирует исключение IllegalArgumentException, и вы можете поймать его вот так:

      boolean isSettingCodeValid = true;

       try {
            SettingCode.valueOf(settingCode.toUpperCase());
        } catch (IllegalArgumentException e) {
            // throw custom exception or change the isSettingCodeValid value
            isSettingCodeValid = false;
        }
person Sarvar Nishonboyev    schedule 03.12.2019

Вы также можете использовать Guava и сделать что-то вроде этого:

// This method returns enum for a given string if it exists, otherwise it returns default enum.
private MyEnum getMyEnum(String enumName) {
  // It is better to return default instance of enum instead of null
  return hasMyEnum(enumName) ? MyEnum.valueOf(enumName) : MyEnum.DEFAULT;
}

// This method checks that enum for a given string exists.
private boolean hasMyEnum(String enumName) {
  return Iterables.any(Arrays.asList(MyEnum.values()), new Predicate<MyEnum>() {
    public boolean apply(MyEnum myEnum) {
      return myEnum.name().equals(enumName);
    }
  }); 
}

Во втором методе я использую библиотеку guava (Google Guava), которая предоставляет очень полезные Iterables класс. Используя метод Iterables.any (), мы можем проверить, существует ли данное значение в объекте списка. Для этого метода требуются два параметра: список и объект Predicate. Сначала я использовал метод Arrays.asList () для создания списка со всеми перечислениями. После этого я создал новый объект Predicate, который используется для проверки того, удовлетворяет ли данный элемент (в нашем случае enum) условию в методе apply. Если это произойдет, метод Iterables.any () вернет истинное значение.

person Dawid Stępień    schedule 15.04.2015