在使用枚举之前检查有效的枚举值

我正在尝试查找枚举集,知道通常会有一个不匹配的exception抛出:我想在执行查找之前检查值是否存在以避免exception。 我的枚举看起来像这样:

public enum Fruit { APPLE("apple"), ORANGE("orange"); ; private final String fruitname; Fruit(String fruitname) { this.fruitname = fruitname; } public String fruitname() {return fruitname;} } 

我想在尝试使用相关的枚举之前检查“香蕉”是否是我的枚举值之一。 我可以迭代比较我的字符串的允许值

 Fruit.values()[i].fruitname 

但我希望能够做类似的事情(pseduo-code):

 if (Fruit.values().contains(myStringHere)) {... 

那可能吗? 我应该完全使用其他东西吗(Arrays?Maps?)?

编辑:最后我和NawaMan的建议一起去了,但感谢所有人提供了有用的建议。

我真的不知道内置的解决方案。 所以你可能必须自己编写一个静态方法。

 public enum Fruit { ... static public boolean isMember(String aName) { Fruit[] aFruits = Fruit.values(); for (Fruit aFruit : aFruits) if (aFruit.fruitname.equals(aName)) return true; return false; } ... } 

有一个apache commons lang EnumUtils.isValidEnum()。 不幸的是,在幕后,这是使用try / catch逻辑并返回布尔值,但至少你的代码看起来很干净:

 if(EnumUtils.isValidEnum(Fruit.class, fruitname)) { .... 

您将需要使用最新的commons-lang3库作为commons-lang 2.x没有此function。

当我这样做时,我通常把它移植到我的枚举课上。

 public enum Fruit { APPLE("apple"), ORANGE("orange"); // Order of initialisation might need adjusting, I haven't tested it. private static final Map lookup = new HashMap(); private final String fruitname; Fruit(String fruitname) { this.fruitname = fruitname; lookup.put(fruitname, Fruit); } public String fruitname() {return fruitname;} public static Fruit fromFruitname(String fruitname) { return lookup.get(fruitname); } } 

但:

  • 对于小型枚举,单步列表可能更有效。

偶然:

  • 在这种情况下,我会使用约定并使用name(),因为它与自定义名称相同,除了案例(容易修复)。
  • 当您需要查找的内容与name()值完全不同时,此解决方案更有用。

这是使用EnumSet.allOf填充地图的方法:

 public enum Fruit { APPLE("apple"), ORANGE("orange"); private static final Map nameToValueMap = new HashMap(); static { for (Fruit value : EnumSet.allOf(Fruit.class)) { nameToValueMap.put(value.name(), value); } } private final String fruitname; Fruit(String fruitname) { this.fruitname = fruitname; } public String fruitname() { return fruitname; } public static Fruit forName(String name) { return nameToValueMap.get(name); } } 

我会成为这里的逆势者…我认为你的第一个冲动(抛出exception)是正确的做法。

如果您在业务逻辑而不是UI中进行检查,则该级别不会向用户提供任何反馈。 (如果您没有检查用户界面,我们还有其他问题)。 因此,处理它的正确方法是抛出exception。

当然,这并不意味着您必须将exception气泡升级到UI级别,从而使其余逻辑短路。 我通常做的是将枚举赋值放在它自己的小try-catch中,并通过重新分配或者你设计的任何其他优雅解决方案来处理exception。

简而言之……你第一次想到的就是钱。 去吧。 只需更改您的exception处理有点不同。

这是我的解决方案。 我创建了一个集合,因此您不必指定构造函数。 这也有额外的好处,即查找的值必须与枚举的情况相匹配。

 public enum Fruit{ Apple, Orange; private final static Set values = new HashSet(Fruit.values().length); static{ for(Fruit f: Fruit.values()) values.add(f.name()); } public static boolean contains( String value ){ return values.contains(value); } } 

在java8中,你可以这样做

  public static boolean isValidFruit(final String fruit) { return Arrays.stream(Fruit.values()) .map(Fruit::name) .collect(Collectors.toSet()) .contains(fruit); } 

也许你根本不应该使用Enum? 如果你经常不得不处理你的枚举中没有定义的值,也许你应该使用像HashMap 之类的东西,然后你可以使用containsKey()来查明是否存在特定的键。

我同意你没有创造任何例外的愿望。 它对性能有好处(作为一个例外值得一千个指令,用于构建堆栈跟踪),并且当你说通常是没有找到它时(因此,它不是一个例外条件)是合乎逻辑的。


我认为你提到的for loop是正确的,如果你只有几个枚举值。 它可能会拥有最好的性能。 但我明白你不想要它。


您可以构建一个Map来查找枚举值,这将避免exception并同时返回相应的枚举。

更新:Trejkaz已经发布了执行此操作的代码。


还要注意,有时候,当没有实例匹配时,不是返回null作为返回类型,而是一些枚举有一个专用实例(例如,将其称为EMPTY或NOT_FOUND)。 优点是所有调用代码都不必处理空值,并且不存在NullPointerException 。 如果需要,可以使用一个名为isFound()的布尔方法(除了该实例外,返回true)。 并且真正需要将这些值与其他值区分开来的代码仍然可以,而那些不关心的代码只是在不知道这种特殊情况的情况下传递实例。

只是提到另一种可能性,即让你的调用代码不必担心exception或条件检查就是总是返回一个Fruit。 如果找不到该字符串,则返回Fruit.UNKNOWN。

例:

 public enum Fruit { public Fruit getValueOf(String name) { for (Fruit fruit : Fruit.values()) { if (fruit.fruitname.equals(name)) return fruit; } } return UNKNOWN; } ... } 

在Oracle JDK(使用JDK 10.0.1)中,类Class具有字段enumConstantDirectory 。 此字段的类型为Map用于Class 。 它按名称存储枚举T的常量。 枚举类初始化后, enumConstantDirectory仍为空。 在第一次调用Enum.valueOf(Class enumType, String name)时,给定枚举T所有常量都存储在enumConstantDirectory

由于每个枚举类都有自己的映射,我们可以尝试使用它,而不是为/ some / every enum / s创建额外的本地映射。

我首先实现了一个实用程序类:

  public class Enums { private static final Field DIRECTORY_FIELD; static { try { DIRECTORY_FIELD = Class.class.getDeclaredField("enumConstantDirectory"); } catch (Exception e) { throw new RuntimeException(e); } } public static > T valueOfOrDefault(Class enumType, String name, T defaultValue) throws Exception { return getEnumConstantDirectory(enumType).getOrDefault(name, defaultValue); } public static > boolean hasValueFor(Class enumType, String name) throws Exception { Map enumConstantDirectory = getEnumConstantDirectory(enumType); return enumConstantDirectory.containsKey(name); } private static > Map getEnumConstantDirectory(Class enumType) throws Exception { try { DIRECTORY_FIELD.setAccessible(true); Map enumConstantDirectory = (Map) DIRECTORY_FIELD.get(enumType); return enumConstantDirectory; } finally { DIRECTORY_FIELD.setAccessible(false); } } } 

它可以像这样使用:

  public enum Note { DO, RE, MI, FA, SOL, LA, SI; static { Enum.valueOf(Note.class, Note.DO.name()); } public static Note valueOfOrDefault(String name, Note defaultValue) throws Exception { return Enums.valueOfOrDefault(Note.class, name, defaultValue); } public static > boolean hasValueFor(String name) throws Exception { return Enums.hasValueFor(Note.class, name); } } 

总结一下:
通常可以检查名称是否表示没有附加映射的枚举常量或迭代枚举常量。 但是,与reflection一样,存在已知的缺点。 另外,需要确保枚举的常量存储在它的类中。