Есть ли способ проверить, существует ли перечисление, сравнив его с заданной строкой? Кажется, я не могу найти такой функции. Я мог бы просто попробовать использовать метод valueOf
и перехватить исключение, но меня учили, что перехват исключений времени выполнения - не лучшая практика. У кого-нибудь есть идеи?
Проверьте, существует ли перечисление в Java
Ответы (9)
Я не думаю, что есть встроенный способ сделать это без перехвата исключений. Вместо этого вы можете использовать что-то вроде этого:
public static MyEnum asMyEnum(String str) {
for (MyEnum me : MyEnum.values()) {
if (me.name().equalsIgnoreCase(str))
return me;
}
return null;
}
Изменить. Как отмечает Джон Скит, values()
работает путем клонирования частного массива резервных копий при каждом его вызове. Если производительность критична, вы можете вызвать values()
только один раз, кэшировать массив и перебирать его.
Кроме того, если ваше перечисление имеет огромное количество значений, альтернатива карты Джона Скита, вероятно, будет работать лучше, чем любая итерация массива.
Если мне нужно это сделать, я иногда создаю 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"));
}
}
values()
создает и заполняет новый массив? Я не помню, чтобы слышал это раньше, но, возможно, у вас есть источник?
- person Michael Myers; 23.07.2009
values()
, был задан, и на него ответили в течение последнего дня: stackoverflow.com/questions/1163076/.
- person Michael Myers; 23.07.2009
String
не интернирована.)
- person Tom Hawtin - tackline; 23.07.2009
Одна из моих любимых библиотек: 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);
}
Я не знаю, почему кто-то сказал вам, что перехват исключений во время выполнения - это плохо.
Использование valueOf
и перехват IllegalArgumentException
отлично подходит для преобразования / проверки строки в перечисление.
EnumUtils.isValidEnum
, чтобы сделать его как можно более чистым. И угадайте, как EnumUtils.isValidEnum
реализовано - ловить, конечно, IllegalArgumentException
:-)
- person Michal Aron; 25.11.2015
map.get
должен генерировать исключение, если ключ тоже отсутствует? Потенциально было бы чище иметь два отдельных метода - один бросающий, а другой нет - но, хотя есть только один метод, я думаю, что гораздо чище, если бы он не бросал.
- person Jon Skeet; 29.08.2019
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, но на самом деле вы могли бы использовать карту нормалей, я думаю, поскольку карта никогда не возвращается ...
В большинстве ответов предлагается либо использовать цикл с 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 намного менее эффективен, особенно с небольшими перечислениями.
Начиная с 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();
}
}
Просто используйте метод valueOf (). Если значение не существует, оно генерирует исключение IllegalArgumentException, и вы можете поймать его вот так:
boolean isSettingCodeValid = true;
try {
SettingCode.valueOf(settingCode.toUpperCase());
} catch (IllegalArgumentException e) {
// throw custom exception or change the isSettingCodeValid value
isSettingCodeValid = false;
}
Вы также можете использовать 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 () вернет истинное значение.