检查枚举类型是否包含给定名称的常量。

7

我在代码中有多个枚举类型:

public enum First { a, b, c, d; }

public enum Second { e, f, g; }

我希望有一个方法,可以使用valueOf()检查任何枚举类型中是否存在某个值,而不需要为每个枚举类型编写一个方法。例如(此代码无法运行):

public boolean enumTypeContains(Enum e, String s) {
    try {
        e.valueOf(s);
    } catch (IllegalArgumentException iae) {
        return false;
    }
    return true;
}

使用方法:

enumTypeContains(First,"a"); // returns true
enumTypeContains(Second,"b"); // returns false

有没有关于如何做类似这样的事情的想法?

我将所有三个答案(例如其中的代码)添加到了我的 github 代码库 stackoverflow-examples 中。 - Paŭlo Ebermann
3个回答

10

这应该可以工作:

public <E extends Enum<E>> boolean enumTypeContains(Class<E> e, String s) {
    try {
        Enum.valueOf(e, s);
        return true;
    }
    catch(IllegalArgumentException ex) {
        return false;
    }
}

然后你需要通过调用它来使用它:

enumTypeContains(First.class, "a");

我不确定简单地搜索值(如jinguy的答案中所述)是否比创建并抛出异常更快。这将取决于您有多频繁地获得false,您有多少个常量以及堆栈跟踪的长度(例如调用深度有多深)。

如果您需要经常执行此操作(对于同一枚举),最好创建一个HashSet或HashMap,将名称映射到枚举值。


正因为此,它需要将实际的枚举类型作为第一个参数。 - Paŭlo Ebermann
哎呀,我的回答之前已经有三个了 :-) - Paŭlo Ebermann
我在编译时遇到了困难 - 出现了以下错误: > 预期 public boolean enumTypeContains(Class<E extends Enum<E>> e, String s) { - BugsPray
1
E必须被定义为方法类型的参数:public <E extends Enum<E>> boolean enumTypeContains(Class<E> e, String s)。我已经修正了答案。 - BalusC
1
确实它有效。现在的问题是哪个提出的解决方案更好;) - BalusC
显示剩余5条评论

8
以下是您的解决方案:
public static boolean contains(Class<? extends Enum> clazz, String val) {
   Object[] arr = clazz.getEnumConstants();
   for (Object e : arr) {
      if (((Enum) e).name().equals(val)) {
         return true;
      }
   }
   return false;
}

它获取枚举的所有值,并检查是否有一个名称与您传递的参数相同的值。如果是,则返回true,否则返回false。
此已经过测试并且有效。测试代码:
public class Test {
   public static void main(String[] args) {
      System.out.println(contains(Haha.class, "Bad"));
      System.out.println(contains(Haha.class, "Happy"));
      System.out.println(contains(Haha.class, "Sad"));
   }

   static enum Haha {
      Happy, Sad;
   }
}

输出:

false
true
true

3
你可以在编译时将其限制为enum类型,方法是将第一个参数的类型设置为Class<? extends Enum<?>>。这样就不需要进行任何运行时检查了。 - ColinD
@Colin,谢谢你的提示。不确定为什么我之前没有想到过。 - jjnguy
@BugsPray:现在请测量一下哪种方法对于您的常规用例更快(例如,您实际在程序中使用的枚举类型和您实际用于检查此类枚举类型的名称)。抛出异常的变体会有一些开销,这取决于堆栈深度(假设valueOf方法具有快速实现),此变体需要复制值数组并循环遍历它。 - Paŭlo Ebermann
我的使用情况并不依赖于效率或速度,因为该函数只会运行大约10次左右。看起来这个解决方案比其他方案快得多。我进行了简单的测试(运行了3000000次函数),而另一个解决方案的运行时间要少20倍左右。它似乎呈几何级数增长。 - BugsPray
我的测试中,两个参数都不是变量。我不知道是否存在任何缓存或其他情况。你(第三个回答者)的答案在这个测试中与这个答案的复杂度相同。 - BugsPray
显示剩余3条评论

3

我发现了另一种变体,它既不需要迭代所有枚举常量,也不需要抛出异常...但它是实现特定的,即使用未记录的方法。 它适用于Sun的JDK 1.6.0_20(并且是通过阅读来自1.6.0_13的源代码制作的)。

public static <E extends Enum<E>> boolean enumTypeContains(Class<E> e, String s) {
    try {
        Method enumDir = Class.class.getDeclaredMethod("enumConstantDirectory");
        enumDir.setAccessible(true);
        Map<?,?> dir = (Map<?,?>)enumDir.invoke(e);
        return dir.containsKey(s);
    }
    catch(NoSuchMethodException ex) {
        throw new Error(ex);
    }
    catch(IllegalAccessException ex) {
        throw new Error(ex);
    }
    catch(InvocationTargetException ex) {
        throw new Error(ex.getCause());
    }
}

如果我们在java.lang包中,代码可能看起来像这样:
public static <E extends Enum<E>> boolean enumTypeContains(Class<E> e, String s) {
    return e.enumConstantDirectory().containsKey(s);
}

这里使用了 Class.enumConstantDirectory() 方法,它会在第一次调用时创建一个包含所有枚举常量的映射表,其中键为枚举常量的名称,值为枚举常量本身。(我们也可以手动创建这样的映射表。)

这个方法被 Enum.valueOf(Class, String) 内部使用,据说也被 EnumType.valueOf(String) 使用(取决于编译器)。

在第一次调用 enumConstantDirectorygetEnumConstants() 时,一个私有的辅助方法会调用 EnumType.values() (由编译器实现)。然后将结果重复使用于这两个方法的后续调用。


需要在 java.lang 中使用 enumConstantDirectory() 的规范解决了我长期以来的一个问题 ☺ - nikodaemus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接