无论如何通过将枚举与给定字符串进行比较来检查枚举是否存在?我似乎找不到任何这样的功能。我可以尝试使用该valueOf
方法并捕获异常,但我被告知捕获运行时异常不是一个好习惯。有人有什么想法吗?
10 回答
如果我需要这样做,我有时会建立一个Set<String>
名称,甚至是我自己的名称Map<String,MyEnum>
——然后你可以检查一下。
有几点值得注意:
- 在静态初始化程序中填充任何此类静态集合。不要使用变量初始化器,然后在枚举构造函数运行时依赖它已经被执行——它不会是!(枚举构造函数是首先要执行的,在静态初始化之前。)
- 尽量避免
values()
频繁使用 - 它每次都必须创建和填充一个新数组。要遍历所有元素,使用EnumSet.allOf
which 对于没有大量元素的枚举更有效。
示例代码:
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"));
}
}
我认为没有一种内置的方法可以在不捕获异常的情况下做到这一点。你可以改用这样的东西:
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 替代方法可能比任何数组迭代执行得更好。
我最喜欢的库之一: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
非常适合将字符串转换/检查为枚举。
根据 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") );
}
}
请注意,我使用 Guava 制作了 ImmutableMap 但实际上您可以使用我认为的法线贴图,因为该贴图永远不会返回...
大多数答案建议要么使用带有等于的循环来检查枚举是否存在,要么使用带有 enum.valueOf() 的 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:尝试/捕获:760ms | 迭代:62ms
- 20:尝试/捕获:1671ms | 迭代:177ms
- 50:尝试/捕获:3113ms | 迭代:488ms
- 100:尝试/捕获:6834ms | 迭代:1760ms
这些结果并不准确。再次执行时,结果会出现高达 10% 的差异,但它们足以表明 try/catch 方法的效率要低得多,尤其是对于小的枚举。
从 Java 8 开始,我们可以使用流而不是 for 循环。此外,如果枚举没有具有此类名称的实例,则返回Optional可能是合适的。
关于如何查找枚举,我提出了以下三种选择:
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;
}
您还可以使用 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);
}
});
}
在第二种方法中,我使用提供了非常有用的Iterables类的guava ( Google Guava ) 库。使用Iterables.any()方法,我们可以检查给定值是否存在于列表对象中。此方法需要两个参数:一个列表和Predicate对象。首先,我使用Arrays.asList()方法创建一个包含所有枚举的列表。之后,我创建了新的Predicate对象,用于检查给定元素(在我们的例子中是枚举)是否满足apply方法中的条件。如果发生这种情况,方法Iterables.any()返回真值。
这是我用来检查是否存在具有给定名称的枚举常量的方法:
java.util.Arrays.stream(E.values()).map(E::name).toList().contains("");
(假设您的枚举称为 E。)在“”中,您应该输入一个枚举常量的名称,您希望检查它是否在枚举中定义。这当然不是最好的解决方案,因为它将数组转换为 Stream,然后再转换为 List,但它又好又短,对我来说效果很好。
正如其他人所提到的,由于您在 2009 年提出了这个问题,因此自 2009 年以来,这在您的情况下将不起作用(除非您迁移到较新版本的 Java)。Java 不支持此答案中使用的功能。但无论如何我都会发布,以防有人使用较新版本的 Java 想要这样做。