3
public enum Gender{

    m("male"), f("female");

    private final String value;

    private Gender(String option){
          value = option;
    }
}

May I know how should I turn a string "male" to any enum? Yes the value is different from the enum. this will not work

 Gender.valueOf("male"); 

I was thinking of providing a

1) parse with for-loop

or

2) static initialize a Map

...

I feel that the 2nd way is better because when I am initializing the map, I can throw run-time exception if the same String exist.

What are the advantages and disadvantages or is there any other better solution?

3
  • 1
    using the second option is better and it is used numerous number of times. Commented Mar 7, 2013 at 13:35
  • Why not using Gender.valueOf("m")? Commented Mar 7, 2013 at 13:35
  • because those values are run-time initialized.. m(Constants.male()); @NarendraPathai any example? or defined patterned? Commented Mar 7, 2013 at 13:36

3 Answers 3

5

I'd use a map:

public enum Gender {

    m("male"), f("female");

    private final String value;

    private static final Map<String, Gender> values = new HashMap<String, Gender>();
    static {
        for (Gender g : Gender.values()) {
            if (values.put(g.value, g) != null) {
                  throw new IllegalArgumentException("duplicate value: " + g.value);
            }
        }
    }

    private Gender(String option) {
          value = option;
    }

    public static Gender fromString(String option) {
        return values.get(option);
    }
}

I see two advantages of this over the first approach:

  1. This method can convert a string to Gender in O(1) time, whereas the other approach requires O(n) time.
  2. This will automatically detect duplicate values.
Sign up to request clarification or add additional context in comments.

1 Comment

I feel so too, just want to confirm it. heehee
2

This is a more generalized solution. I use it anytime an enum is mapped to a code value. It is especially helpful when you have an enum represent a series of discrete values that map, say, to a database table. Note that the next few types that are defined only have to be written once and then can be used on any enum.

You first define an interface that your enum (or a POJO to be stored in a collection) will implement:

public interface Coded<T> {
    T getCode();
}

The following leverages Google Guava, however, you could perform null checks rather than using their Optional class:

public final class CodedFinder {

    private CodedFinder() {}

    public static <V, T extends Enum<T> & Coded<V>> T find(final Class<T> target, final V code) {
        final Optional<T> found = findInternal(Arrays.asList(target.getEnumConstants()), code);
        if (! found.isPresent()) {
            throw new IllegalArgumentException(code.toString() + " is invalid for " + target.getSimpleName());
        }
        return found.get();
    }

    // Additional find methods for arrays and iterables redacted for clarity.

    private static <V, T extends Coded<V>> Optional<T> findInternal(final Iterable<T> values, final V code) {
        return Iterables.tryFind(values, CodedPredicate.of(code));
    }
}

The method above uses Class#getEnumConstants to retrieve all the values defined in the enum. The actual find method that is being called can be used not just for enums, but also for array and collections, etc.

We have to define a Predicate to leverage Guava's find method:

public final class CodedPredicate<V, T extends Coded<V>> implements com.google.common.base.Predicate<T> {
    private final V value;

    private CodedPredicate(final V value) {
        this.value = value;
    }

    public static <V, T extends Coded<V>> CodedPredicate<V, T> of(final V value) {
        return new CodedPredicate<V, T>(value);
    }

    public boolean apply(final T current) {
        return value.equals(current.getCode());
    }
}

That predicate is generic, so you can use Coded<Integer> or any other POJO that has a sensible equals() implementation.

It seems like a lot of code, but really there are only three types defined above, and these can be shared across any number of projects. Whenever you want to search on a value, it becomes trivial:

public final class GenderTest {

    @Test(groups="unit")
    public static void testValid() {

        assert CodedFinder.find(Gender.class, "male")  == Gender.m;
        assert CodedFinder.find(Gender.class, "female") == Gender.f;
    }

    @Test(groups="unit", expectedExceptions=IllegalArgumentException.class)
    public static void testInvalid() {

        CodedFinder.find(Gender.class, "foo");
    }

    public enum Gender implements Coded<String> {

        m("male"), f("female");

        private final String value;

        private Gender(final String option) {
              value = option;
        }

        public String getCode()
        {
            return value;
        }
    }
}

1 Comment

Wow, really nice! It looks a bit over-engineered but the caller's code is very simple. Anyways, you should also utilize guava cache so that it runs in O(1) instead of O(n)
0

I've come across this situation a few times, and usually use your first approach:

public static Gender forValue(String value) {
    for (Gender gender : gender.values()) {
        if (gender.value.equals(value)) {
            return gender;
        }
    }

    return null; // or throw something like IllegalArgumentException 
}

Seen as your value is declared final and all instances need to be declared from code, you are always responsible for the values being unique, so for me the issue is close to nonexistent.

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.