简体   繁体   English

检查枚举是否存在于 Java 中

[英]Check if enum exists in Java

Is there anyway to check if an enum exists by comparing it to a given string?无论如何通过将枚举与给定字符串进行比较来检查枚举是否存在? I can't seem to find any such function.我似乎找不到任何这样的功能。 I could just try to use the valueOf method and catch an exception but I'v been taught that catching runtime exceptions is not good practice.我可以尝试使用valueOf方法并捕获异常,但有人告诉我捕获运行时异常不是好的做法。 Anybody have any ideas?有人有任何想法吗?

If I need to do this, I sometimes build a Set<String> of the names, or even my own Map<String,MyEnum> - then you can just check that.如果我需要这样做,我有时会构建一个Set<String>名称,甚至是我自己的Map<String,MyEnum> - 然后你可以检查一下。

A couple of points worth noting:有几点值得注意:

  • Populate any such static collection in a static initializer.在静态初始值设定项中填充任何此类静态集合。 Don't use a variable initializer and then rely on it having been executed when the enum constructor runs - it won't have been!不要使用变量初始值设定项,然后依赖它在枚举构造函数运行时已执行 - 它不会! (The enum constructors are the first things to be executed, before the static initializer.) (枚举构造函数是在静态初始化器之前首先执行的事情。)
  • Try to avoid using values() frequently - it has to create and populate a new array each time.尽量避免频繁使用values() - 它每次都必须创建并填充一个新数组。 To iterate over all elements, use EnumSet.allOf which is much more efficient for enums without a large number of elements.要迭代所有元素,请使用EnumSet.allOf ,这对于没有大量元素的枚举更有效。

Sample code:示例代码:

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

Of course, if you only have a few names this is probably overkill - an O(n) solution often wins over an O(1) solution when n is small enough.当然,如果你只有几个名字,这可能有点矫枉过正——当 n 足够小时,O(n) 解决方案通常会胜过 O(1) 解决方案。 Here's another approach:这是另一种方法:

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

I don't think there's a built-in way to do it without catching exceptions.我不认为有一种内置的方法可以在不捕获异常的情况下做到这一点。 You could instead use something like this:你可以改用这样的东西:

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

Edit: As Jon Skeet notes, values() works by cloning a private backing array every time it is called.编辑:正如 Jon Skeet 所指出的, values()通过在每次调用时克隆一个私有后备数组来工作。 If performance is critical, you may want to call values() only once, cache the array, and iterate through that.如果性能很重要,您可能只想调用values()一次,缓存数组,然后遍历它。

Also, if your enum has a huge number of values, Jon Skeet's map alternative is likely to perform better than any array iteration.此外,如果您的枚举具有大量值,则 Jon Skeet 的 map 替代方案可能比任何数组迭代执行得更好。

One of my favorite lib: Apache Commons.我最喜欢的库之一:Apache Commons。

The EnumUtils can do that easily. EnumUtils可以轻松做到这一点。

Following an example to validate an Enum with that library:以下是使用该库验证 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);
}

I don't know why anyone told you that catching runtime exceptions was bad.我不知道为什么有人告诉你捕获运行时异常很糟糕。

Use valueOf and catching IllegalArgumentException is fine for converting/checking a string to an enum.使用valueOf并捕获IllegalArgumentException可以很好地将字符串转换/检查为枚举。

Based on Jon Skeet answer i've made a class that permits to do it easily at work:根据 Jon Skeet 的回答,我制作了一个可以在工作中轻松完成的课程:

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>(lorber.sebastien@gmail.com)</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);
    }

}

And the unit test:和单元测试:

import org.testng.annotations.Test;

import static org.testng.Assert.*;


/**
 * @author Sebastien Lorber <i>(lorber.sebastien@gmail.com)</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") );
    }

}

Notice that i used Guava to make an ImmutableMap but actually you could use a normal map i think since the map is never returned...请注意,我使用 Guava 制作了一个 ImmutableMap 但实际上你可以使用我认为的法线贴图,因为该贴图永远不会返回......

Most of the answers suggest either using a loop with equals to check if the enum exists or using try/catch with enum.valueOf().大多数答案建议要么使用带有 equals 的循环来检查枚举是否存在,要么使用带有 enum.valueOf() 的 try/catch。 I wanted to know which method is faster and tried it.我想知道哪种方法更快并尝试了它。 I am not very good at benchmarking, so please correct me if I made any mistakes.我不是很擅长基准测试,所以如果我犯了任何错误,请纠正我。

Heres the code of my main class:这是我的主类的代码:

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

This means, each methods run 50000 times the lenght of the enum I ran this test multiple times, with 10, 20, 50 and 100 enum constants.这意味着,每个方法运行的枚举长度是我多次运行此测试的枚举长度的 50000 倍,使用 10、20、50 和 100 个枚举常量。 Here are the results:结果如下:

  • 10: try/catch: 760ms | 10:尝试/捕获:760ms | iteration: 62ms迭代:62ms
  • 20: try/catch: 1671ms | 20:尝试/捕获:1671ms | iteration: 177ms迭代:177ms
  • 50: try/catch: 3113ms | 50:尝试/捕获:3113ms | iteration: 488ms迭代:488ms
  • 100: try/catch: 6834ms | 100:尝试/捕获:6834ms | iteration: 1760ms迭代:1760ms

These results were not exact.这些结果并不准确。 When executing it again, there is up to 10% difference in the results, but they are enough to show, that the try/catch method is far less efficient, especially with small enums.再次执行时,结果有高达 10% 的差异,但它们足以表明 try/catch 方法的效率要低得多,尤其是对于小枚举。

Since Java 8, we could use streams instead of for loops.从 Java 8 开始,我们可以使用代替 for 循环。 Also, it might be apropriate to return an Optional if the enum does not have an instance with such a name.此外,如果枚举没有具有此类名称的实例,则返回Optional可能是合适的。

I have come up with the following three alternatives on how to look up an enum:关于如何查找枚举,我提出了以下三种替代方法:

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

Just use valueOf() method.只需使用 valueOf() 方法。 If the value doesn't exist, it throws IllegalArgumentException and you can catch it like that:如果该值不存在,它会抛出 IllegalArgumentException 并且您可以像这样捕获它:

      boolean isSettingCodeValid = true;

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

You can also use Guava and do something like this:您还可以使用 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);
    }
  }); 
}

In second method I use guava ( Google Guava ) library which provides very useful Iterables class.在第二种方法中,我使用 guava ( Google Guava ) 库,它提供了非常有用的Iterables类。 Using the Iterables.any() method we can check if a given value exists in a list object.使用Iterables.any()方法,我们可以检查给定值是否存在于列表对象中。 This method needs two parameters: a list and Predicate object.该方法需要两个参数:一个列表和Predicate对象。 First I used Arrays.asList() method to create a list with all enums.首先,我使用Arrays.asList()方法创建一个包含所有枚举的列表。 After that I created new Predicate object which is used to check if a given element (enum in our case) satisfies the condition in apply method.之后我创建了新的Predicate对象,用于检查给定元素(在我们的例子中是枚举)是否满足apply方法中的条件。 If that happens, method Iterables.any() returns true value.如果发生这种情况,方法Iterables.any()将返回真值。

Here is what I use to check if an enum constant with given name exists:这是我用来检查具有给定名称的枚举常量是否存在的方法:

java.util.Arrays.stream(E.values()).map(E::name).toList().contains("");

(Suppose your enum is called E.) Here inside "" you should put a name of an enum constant for which you wish to check if it is defined in the enum or not. (假设您的枚举名为 E。)在 "" 中,您应该输入一个枚举常量的名称,您希望检查它是否在枚举中定义。 This is certainly not the best possible solution since it converts an array into Stream and then again into List, but is nice and short and it works fine for me.这当然不是最好的解决方案,因为它将数组转换为 Stream,然后再转换为 List,但它既美观又简短,对我来说效果很好。

As other people mentioned, since you asked this question in 2009., this will not work in your case (unless you migrated to a newer version of Java) since in 2009. Java did not support features used in this answer.正如其他人所提到的,由于您在 2009 年提出了这个问题,因此自 2009 年以来,这不适用于您的情况(除非您迁移到较新版本的 Java)。Java 不支持此答案中使用的功能。 But I am posting anyway in case someone with newer version of Java wants to do this.但无论如何我都会发布以防万一拥有较新版本 Java 的人想要这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM