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

时间:2009-10-02 13:49:13

标签: java enums

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

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)) {...

这可能吗?我应该完全使用其他东西吗(数组?地图?)?

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

12 个答案:

答案 0 :(得分:25)

我真的不知道内置的解决方案。因此,您可能必须自己编写静态方法。

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;
   }
   ...
}

答案 1 :(得分:21)

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

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

你需要使用最新的commons-lang3库作为commons-lang 2.x没有这个功能。

答案 2 :(得分:8)

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

public enum Fruit {
        APPLE("apple"),
        ORANGE("orange");

    // Order of initialisation might need adjusting, I haven't tested it.
    private static final Map<String, Fruit> lookup = new HashMap<String, Fruit>();
    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()值完全不同时,此解决方案更有用。

答案 3 :(得分:7)

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

public enum Fruit {

    APPLE("apple"), 
    ORANGE("orange");

    private static final Map<String, Fruit> nameToValueMap = new HashMap<String, Fruit>();

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

答案 4 :(得分:6)

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

public enum Fruit{
    Apple, 
    Orange;

    private final static Set<String> values = new HashSet<String>(Fruit.values().length);

    static{
        for(Fruit f: Fruit.values())
            values.add(f.name());
    }

    public static boolean contains( String value ){
        return values.contains(value);
    }

}

答案 5 :(得分:5)

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

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

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

简而言之......你第一次想到的就是赚钱了。去吧。只需将您的异常处理更改一点即可。

答案 6 :(得分:3)

在java8中你可以这样做

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

答案 7 :(得分:2)

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


如果您只有少量枚举值,我认为您提到的for loop是正确的。它可能会拥有最好的性能。但我知道你不想要它。


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

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


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

答案 8 :(得分:2)

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

答案 9 :(得分:2)

只是提到另一种可能性,即让你的调用代码不必担心异常或条件检查就是总是返回一个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;
   }
   ...
}

答案 10 :(得分:1)

您也可以这样: 将所有枚举都放在一个类中,例如:

public class EnumProto {

    public static Class<?>[] l;

    public static enum Severity {
        UNKNOWN_SEVERITY
    }

    public static  enum UserType {
        UNKNOWN_USER_TYPE,
        INTERNAL_EMPLOYEE ,
        EXTERNAL_PARTY
    }

    public static enum Channel {
        UNKNOWN_CHANNEL,
        CALL,
        EMAIL,
        WEBFORM,
        FAX
    }

//You can add more enum classes
}

在另一个泛型类中,您可以具有以下内容:

public class Mapper {
    /**
     * This method returns all names of an enum
     * @param e
     * @return
     */
    public static String[] getEnumNames(Class<? extends Enum<?>> e) {
        return Arrays.stream(e.getEnumConstants()).map(Enum::name).toArray(String[]::new);
    }

    /**
     * This method returns all the enum classes from a class
     * @return
     */
    public static Class<?>[] getENumClasses(){
        Class<?>[] x = EnumProto.class.getClasses();
        return x;
    }

    /**
     *This utility performs following:
     *- will get all enum classes from EnumProto
     *- will get all names against all classes
     *- checks against all names of enum class and returns true if name matches else returns false
     * @param enumClass
     * @param value
     * @return
     */
    public static Boolean enumValidator(String enumClass, String value) {
        Boolean bool=false;
        EnumProto.l = getENumClasses();
        for (Class x : EnumProto.l) {
            if (x.getSimpleName().equals(enumClass)) {
                try {
                    String enumNames[] = getEnumNames(x);
                    if ( ArrayUtils.contains( enumNames, value ) ) {
                        bool=true;
                        return bool;
                    }
                } catch (ClassCastException e) {
                }
            }
        }
        return bool;
    }

    /**
     * Driver method for testing purpose
     * @param args
     */
    public static void main(String args[]){
        System.out.println(enumValidator(EnumProto.Channel.class.getSimpleName(),"CALL"));
    }
}

通过这种通用方法,您可以检查传递的字符串是否为枚举之一。

答案 11 :(得分:0)

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

由于每个枚举类都有自己的映射,因此我们可以尝试使用它,而不是为一个/某个/每个枚举创建一个附加的本地映射。

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

enumConstantDirectory

可以这样使用:

  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 extends Enum<T>> T valueOfOrDefault(Class<T> enumType, String name, T defaultValue) throws Exception {
      return getEnumConstantDirectory(enumType).getOrDefault(name, defaultValue);
    }

    public static <T extends Enum<T>> boolean hasValueFor(Class<T> enumType, String name) throws Exception {
      Map<String, T> enumConstantDirectory = getEnumConstantDirectory(enumType);
      return enumConstantDirectory.containsKey(name);
    }

    private static <T extends Enum<T>> Map<String, T> getEnumConstantDirectory(Class<T> enumType) throws Exception {
      try {
        DIRECTORY_FIELD.setAccessible(true);
        Map<String, T> enumConstantDirectory = (Map<String, T>) DIRECTORY_FIELD.get(enumType);
        return enumConstantDirectory;
      }
      finally {
        DIRECTORY_FIELD.setAccessible(false);
      }
    }

  }

总结:
通常可以检查名称是否代表枚举常量,而无需其他映射或遍历枚举常量。但是像往常一样,存在反射的缺点。此外,还需要确保将枚举的常量存储在其类中。