Проверьте, существует ли enum в Java

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

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

Несколько замечаний:

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

Пример кода:

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"));
    }
}

Ответ 3

Один из моих любимых lib: Apache Commons.

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

Вот пример кода для проверки Enum:

MyEnum strTypeEnum = null;

// test if String str is compatible with the enum 
if( EnumUtils.isValidEnum(MyEnum.class, str) ){
    strTypeEnum = MyEnum.valueOf(str);
}

Ответ 4

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

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

Ответ 5

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

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 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);
    }

}

И unit test:

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, но на самом деле вы могли бы использовать обычную карту, о которой я думаю, так как карта никогда не возвращается...

Ответ 6

Вы также можете использовать 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(), мы можем проверить, существует ли данное значение в объекте списка. Для этого метода нужны два параметра: список и Предикат. Сначала я использовал метод Arrays.asList() для создания списка со всеми перечислениями. После этого я создал новый объект Predicate, который используется для проверки того, удовлетворяет ли данный элемент (перечисление в нашем случае) условию в методе применить. Если это произойдет, метод Iterables.any() возвращает истинное значение.

Ответ 7

В большинстве ответов предлагается либо использовать цикл с равными, чтобы проверить, существует ли перечисление, либо с помощью 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 константами enum. Вот результаты:

  • 10: try/catch: 760ms | итерация: 62 мс
  • 20: try/catch: 1671ms | итерация: 177 мс
  • 50: try/catch: 3113ms | итерация: 488 мс
  • 100: try/catch: 6834ms | итерация: 1760 мс

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