davek
davek

Reputation: 22915

Check valid enum values before using enum

I'm trying to lookup against an Enum set, knowing that there will often be a non-match which throws an exception: I would like to check the value exists before performing the lookup to avoid the exceptions. My enum looks something like this:

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

and I want to check if, say, "banana" is one of my enum values before attempting to use the relevant enum. I could iterate through the permissible values comparing my string to

Fruit.values()[i].fruitname

but I'd like to be able to do something like (pseduo-code):

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

Is that possible? Should I be using something else entirely (Arrays? Maps?)?

EDIT: in the end I've gone with NawaMan's suggestion, but thanks to everyone for all the helpful input.

Upvotes: 34

Views: 115486

Answers (13)

Jorge Sanchez
Jorge Sanchez

Reputation: 1629

Arrays.stream(Fruit.values()).anyMatch(e -> e.name().equals(myStringHere))

Upvotes: 0

Ahluwalia Rishi
Ahluwalia Rishi

Reputation: 11

You can also do like this: Have all enums in one class ex:

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
}

In Another generic class you can have something like this:

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

This way with one generic method you can check whether passed string is one of the enum or not.

Upvotes: 1

LuCio
LuCio

Reputation: 5173

In Oracle JDK (tried with JDK 10.0.1) the class Class has the field enumConstantDirectory. This field is of type Map<String, T> for Class<T>. It stores the constants of an enum T by their names. After an enum class has been initialized enumConstantDirectory is still empty. On the first call of Enum.valueOf(Class<T> enumType, String name) all constants of the given enum T are stored in the enumConstantDirectory.

Since every enum class has already its own mapping we could try to utilize it instead of creating an additional local mapping for an/some/every enum/s.

I implemented first a utility class:

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

  }

It can be used like this:

  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 <T extends Enum<T>> boolean hasValueFor(String name) throws Exception {
      return Enums.hasValueFor(Note.class, name);
    }

  }

To summarize:
It's generally possible to check if a name represents an enum constant without additional maps or iterating over the enum constants. But as always with reflections there are the known drawbacks. Additionally it's required to ensure that the constants of an enum are stored in it's class.

Upvotes: 0

charlb
charlb

Reputation: 1249

In java8 you can do it like this

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

Upvotes: 6

Yeison
Yeison

Reputation: 192

This is my solution. I created a set so that you don't have to specify a constructor. This also has the added benefit that the value being looked up has to match the case of the enum.

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

}

Upvotes: 8

kman
kman

Reputation: 510

There is an apache commons lang EnumUtils.isValidEnum(). Unfortunately, Under the hood, this is using try/catch logic and returning boolean, but at least your code looks clean:

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

You will need to use the latest commons-lang3 library as commons-lang 2.x does not have this function.

Upvotes: 35

dogbane
dogbane

Reputation: 274562

This is how you can do it using EnumSet.allOf to populate a map:

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

Upvotes: 7

Hakanai
Hakanai

Reputation: 12670

When I do this I usually graft it onto my enum class.

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

But:

  • For small enums it's probably more efficient to step through the list.

Incidentally:

  • In this situation I would have gone with convention and used name() since it's the same as the custom name except for the case (easily fixed.)
  • This solution is more useful when what you have to look up is completely different to the name() value.

Upvotes: 8

JRL
JRL

Reputation: 77995

Just to mention another possibility that'll let your calling code not have to worry about exceptions or conditional checks is to always return a Fruit. If the string is not found, return Fruit.UNKNOWN, for example.

Example:

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

Upvotes: 2

KLE
KLE

Reputation: 24159

I agree with your desire to have no exception created. It's good for performance (as an exception is worth a thousand instructions, for building the stack trace), and it's logical when you say that it is often the case that it is not found (therefore, it is not an exceptional condition).


I think the for loop you mention is correct, if you have only a few enum values. It will probably have the best performance of all. But I understand you don't want it.


You could build a Map to find your enum values, that would avoid the exception and return the appropriate enum at the same time.

Update : Trejkaz already posted the code that does this.


Also note that sometimes, instead of returning null as the return type when no instance matches, some enum have a dedicated instance for that (call it EMPTY or NOT_FOUND for example). The advantage is that all calling code doesn't have to deal with nulls, and risk no NullPointerException. If needed, there can a boolean method that says isFound()(returns true except for that instance). And codes that would really need to differenciate that values from others still can, while the ones that don't care just pass the instance around without knowledge of this special case.

Upvotes: 2

Tom Jefferys
Tom Jefferys

Reputation: 13310

Perhaps you shouldn't be using an Enum at all? If you're regularly having to deal with values that aren't defined in your Enum, perhaps you should be using something like a HashMap<String,Fruit> You can then use containsKey(), to find out if a particular key exist.

Upvotes: 2

NawaMan
NawaMan

Reputation: 25687

I really don't know a built-in solution. So you may have to write it yourself as a static method.

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

Upvotes: 33

Rap
Rap

Reputation: 7282

I'll be the contrarian here ... I think your first impulse (to throw an exception) is the right thing to do.

If you're checking within the business logic rather than the UI, there won't be any feedback at that level to the user. (If you're not checking in the UI, we have other problems). Therefore, the proper way to handle it is by throwing an exception.

Of course that doesn't mean you have to have the exception bubble up to the UI level thus short-circuiting the rest of your logic. What I usually do it put the enum assignment in its own little try-catch and handle the exception by reassigning or whatever other elegant solution you've devised.

In short ... you were on the money with your first thought. Go with it. Just change your exception handling a little different.

Upvotes: 5

Related Questions