views:

1250

answers:

3

Is there anyway to check if an enum exists by comparing it to a given string? I can't seem to find any such function. I could just try to use the valueOf method and catch an exception but Iv been taught that catching runtime exceptions is not good practice. Anybody have any ideas?

+4  A: 

I don't think there's a built-in way to do it without catching exceptions. You could instead use something like this:

public static MyEnum asMyEnum(String str) {
    for (MyEnum me : MyEnum.values()) {
        if (me.name().equalsIgnoreCase(str))
            return me;
    }
    return null;
}

Edit: As Jon Skeet notes, values() works by cloning a private backing array every time it is called. If performance is critical, you may want to call values() only once, cache the array, and iterate through that.

Also, if your enum has a huge number of values, Jon Skeet's map alternative is likely to perform better than any array iteration.

Michael Myers
I figured I would have to do something like that. Thanks!
Daniel Hertz
+1  A: 

I don't know why anyone told you that catching runtime exceptions was bad.

Use valueOf and catching IllegalArgumentException is fine for converting/checking a string to an enum.

nos
No it's not, IMO. That's testing a non-exceptional situation via exceptions - using them for flow control in a normal, non-error condition. That's a very poor use of exceptions IMO, and one which can have a significant performance impact. Exceptions are fine in terms of performance normally, because they shouldn't happen - but when you use them for non-error conditions, then code which *looks* like it should run quickly can get bogged down.
Jon Skeet
+3  A: 

If I need to do this, I sometimes build a Set<String> of the names, or even my own Map<String,MyEnum> - then you can just check that.

A couple of points worth noting:

  • Populate any such static collection in a static initializer. Don't use a variable initializer and then rely on it having been executed when the enum constructor runs - it won't have been! (The enum constructors are the first things to be executed, before the static initializer.)
  • Try to avoid using values() frequently - it has to create and populate a new array each time. To iterate over all elements, use EnumSet.allOf which is much more efficient for enums without a large number of elements.

Sample code:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

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

    static {
        for (SampleEnum value : EnumSet.allOf(SampleEnum.class)) {
            nameToValueMap.put(value.name(), value);
        }
    }

    public static SampleEnum forName(String name) {
        return nameToValueMap.get(name);
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}

Of course, if you only have a few names this is probably overkill - an O(n) solution often wins over an O(1) solution when n is small enough. Here's another approach:

import java.util.*;

enum SampleEnum {
    Foo,
    Bar;

    // We know we'll never mutate this, so we can keep
    // a local copy.
    private static final SampleEnum[] copyOfValues = values();

    public static SampleEnum forName(String name) {
        for (SampleEnum value : copyOfValues) {
            if (value.name().equals(name)) {
                return value;
            }
        }
        return null;
    }
}

public class Test {
    public static void main(String [] args)
        throws Exception { // Just for simplicity!
        System.out.println(SampleEnum.forName("Foo"));
        System.out.println(SampleEnum.forName("Bar"));
        System.out.println(SampleEnum.forName("Baz"));
    }
}
Jon Skeet
`values()` creates and populates a new array? I don't remember hearing that before, but presumably you have a source?
Michael Myers
Well it returns an array... and it can't protect you from mutating that array... and it wouldn't subsequent callers to be hampered by that. Other than that, look at the JRE sources :)
Jon Skeet
It's not in the JRE sources, that's why I asked. But I hadn't thought of the mutation aspect; you're probably right, then.
Michael Myers
Oops, sorry - yes, just checked myself. Decompile an enum then :) It uses clone() which may be pretty quick, but not as quick as not having to do it at all...
Jon Skeet
Ah, the question of how `values()` works was asked and answered within the last day: http://stackoverflow.com/questions/1163076/how-is-values-implemented-for-java-6-enums/1163121#1163121.
Michael Myers
I bet the cost of clone is minimal to checking through the array (particularly if the target `String` is not interned.)
Tom Hawtin - tackline