【问题标题】:Checking if enum type contains constant with given name检查枚举类型是否包含具有给定名称的常量
【发布时间】:2011-07-27 20:12:34
【问题描述】:

我的代码中有多个枚举:

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

关于如何做这样的事情有什么想法吗?

【问题讨论】:

标签: java generics enums


【解决方案1】:

这应该可行:

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 将名称映射到枚举值。

【讨论】:

【解决方案2】:

这是您的解决方案:

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

【讨论】:

  • 您可以在编译时将其限制为enum 类型,方法是将第一个参数的类型设置为Class&lt;? extends Enum&lt;?&gt;&gt;。然后你就不需要做任何运行时检查了。
  • @Colin,感谢您的提示。不知道为什么我之前没有想到这一点。
  • 我想指出,通常使用异常来操纵程序的流程(例外情况除外)是一个坏主意。
  • @BugsPray:现在请测量哪些对于您的常用用例更快(例如,您在程序中实际使用的枚举类型,以及您实际用于检查的名称)。抛出异常的变体有一些开销取决于堆栈深度(假设valueOf 方法有一个快速实现),这个变体需要复制值数组并循环遍历它。
  • 我的用例不依赖于效率或速度,因为该函数只会运行大约 10 次左右。看起来这个解决方案的工作速度比其他解决方案快得多。通过一个简单的测试(运行函数 3000000 次)运行它,另一个解决方案的运行时间大约减少了 20 倍。它似乎呈几何级数增长。
【解决方案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()(由编译器实现)。然后将结果重用于这两个方法的后续调用。

【讨论】:

  • enumConstantDirectory()需要在java.lang的规范解决了我的一个长期问题☺
猜你喜欢
  • 1970-01-01
  • 2011-12-24
  • 2018-06-16
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
  • 2015-08-21
  • 1970-01-01
  • 2012-08-30
相关资源
最近更新 更多