检查 enum 是否存在于 Java 中

是否可以通过将枚举与给定的字符串进行比较来检查枚举是否存在?我似乎找不到这样的功能。我可以尝试使用 valueOf方法捕获一个异常,但是我已经被告知捕获运行时异常不是一个好的实践。有人知道吗?

128528 次浏览

我不认为有一个内置的方法可以做到这一点,而不捕捉异常:

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

编辑: 正如 Jon Skeet 所指出的,values()每次调用时都会克隆一个私有备份数组。如果性能很关键,那么可能只需要调用 values()一次,缓存数组,然后循环执行。

此外,如果枚举具有大量值,Jon Skeet 的 map 替代方案可能比任何数组迭代都要好。

我不知道为什么有人告诉你捕获运行时异常是不好的。

使用 valueOf并捕获 IllegalArgumentException可以很好地将字符串转换/检查为枚举。

如果我需要这样做,我有时建立一个名称的 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"));
}
}

当然,如果你只有几个名字,这可能是过度杀伤——当 n 足够小时,O (n)解经常胜过 O (1)解。还有一种方法:

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

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


}

单元测试:

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


}

请注意,我使用番石榴制作一个 ImmutableMap,但实际上你可以使用一个正常的地图,我认为,因为地图从来没有返回..。

我最喜欢的图书馆之一: 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);
}

你也可以使用番石榴来做这样的事情:

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

在第二种方法中,我使用了番石榴(谷歌番石榴)库,它提供了非常有用的 无用的东西类。使用 Iterables.any ()方法,我们可以检查列表对象中是否存在给定的值。此方法需要两个参数: list 和 说重点对象。首先,我使用 AsList ()方法创建一个包含所有枚举的列表。之后,我创建了新的 说重点对象,用于检查给定的元素(在我们的例子中是枚举)是否满足 申请方法中的条件。如果发生这种情况,方法 Iterables.any ()返回真值。

大多数答案建议或者使用等于的循环来检查枚举是否存在,或者使用带枚举值的 try/catch ()。我想知道哪种方法更快,并尝试了一下。我不是很擅长基准测试,所以如果我犯了错误请纠正我。

下面是我的主要类的代码:

    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: try/catch: 760ms | 迭代: 62ms
  • 20: try/catch: 1671ms | 迭代: 177ms
  • 50: try/catch: 3113ms | 迭代: 488ms
  • 100: try/catch: 6834ms | 迭代: 1760ms

这些结果并不准确。当再次执行它时,结果会有高达10% 的差异,但这足以表明 try/catch 方法的效率要低得多,特别是对于小枚举。

从 Java8开始,我们可以使用 溪流代替 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,你可以像这样抛出 catch:

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

下面是我用来检查具有给定名称的枚举常量是否存在的内容:

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

(假设您的 enum 被称为 E)在这里的“”中,您应该放置一个枚举常量的名称,您希望检查它是否在枚举中定义。 这当然不是最好的解决方案,因为它将一个数组转换为 Stream,然后再转换为 List,但它很好,很短,对我来说很好用。

正如其他人提到的,自从你在2009年问了这个问题。,这将无法在您的情况下(除非您迁移到一个较新的 Java 版本)自2009年以来。Java 不支持这个答案中使用的特性。但是我还是要发布一下,以防有新版本 Java 的用户想要这么做。

使用 java 8,您可以执行下面这样的操作来检查它是否有效。

Stream.of(MyEnum.values())
.map(MyEnum::name)
.collect(Collectors.toList()).contains(<STRING_YOU_WANT_TO_VALIDATE>)