34

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.

13 Answers 13

36

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.

Sign up to request clarification or add additional context in comments.

6 Comments

checks for null values as well, that is good enough. Thanks your comment was helpful
I did not know about this function. This is really the clearest and easiest way (if you have the dependency)
it doesn't check for the value. e.g. if one has APPLE("apple"), and the call is for apple it will return false. If the call is for APPLE it will return true.
still using in 2020 such as EnumUtils.isValidEnum(Fruit.class, someFood) ? someFood : "";
yes a one-liner like this is a clean solution: return EnumUtils.isValidEnum(Fruit.class, fruitname) ? Fruit.valueOf(fruitname) : null; valueOf method can be used in the same line after this check.
|
33

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

3 Comments

"values()" creates a cloned array each time, so it is best not to call it too often. Call it only once and cache the results, or use "EnumSet.allOf(Fruit.class)".
At JDK 1.7 it was fixed. In JDK 1.5 there is comment to fix this later. Don't know what happen in JDK 1.6.
Note that this solution is slow for many values. Better do something like stackoverflow.com/a/2546726/260805 instead.
8

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.

6 Comments

I took permission to fix the example, having the map as static.
The order of initialization is ok, don't worry.
Yeah, the lack of static was a typo.
I can't get this example to compile as I get "Cannot refer to the static enum field <myclass>.Fruit.lookup within an initializer". If I make the map non-static, then the callout fromFruitname complains with "Cannot make a static reference to the non-static field lookup". What am I missing?
The order of initialisation was wrong. Building the map would need to be moved to the from* method it seems, which is odd because most of my custom typesafe enums did do it from the constructor. There must be something different about enum vs. rolling your own enumeration classes.
|
8

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

}

Comments

7

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

Comments

6

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

1 Comment

Could be even simpler with Java 8. Add a simple method boolean contains(String testedValue) to your enum and return something like this: Arrays.stream(values()).map(Enum::name).anyMatch(code -> code.equals(testedValue));
5

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.

Comments

2

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.

Comments

2

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.

1 Comment

Touche. Good call with the "not an exceptional condition." I was thinking that an exception should be treated as such. If it isn't really an exception, it shouldn't be an exception. +1, @KLE.
2

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

Comments

1

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.

Comments

0

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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.