0

Let say I have two enum classes which implement a Foo interface

public enum Enum1 implements Foo {
  ENUM1_CONST1,
  ENUM1_CONST2;
}

public enum Enum2 implements Foo {
  ENUM2_CONST1,
  ENUM2_CONST2;
}

How can I create a type-safe implementation of a method that takes a string as a parameter which string matches any of the enum constant's name and returns a Foo instance which could be any of the constants from both enum definitions.

In other words: Foo foo1 = holyGrailMethod("ENUM1_CONST1"); Foo foo2 = holyGrailMethod("ENUM1_CONST2"); Foo foo3 = holyGrailMethod("ENUM2_CONST1"); Foo foo4 = holyGrailMethod("ENUM2_CONST2");

jilt3d
  • 3,864
  • 8
  • 34
  • 41
  • 1
    I think enums have a static, synthetic method called `valueOf` that do just that. Isn't so? – Edwin Dalorzo Mar 16 '14 at 12:42
  • Yes, I tried to use that method but for example the following: Enum.valueOf(Foo.class, "ENUM1_CONST1"); gives compile error. – jilt3d Mar 16 '14 at 12:48
  • possible duplicate of [Java - Convert String to enum](http://stackoverflow.com/questions/604424/java-convert-string-to-enum) – donfuxx Mar 16 '14 at 12:51
  • Can you show us how you ware trying to use `Enum.valueOf`? Also what errors did you get? – Pshemo Mar 16 '14 at 13:12
  • The first thing that came to my mind is the following: `public Foo holyGrailMethod(@NonNull final String constantString) { return Enum.valueOf(Foo.class, constantString)}`. The error is: Bound mismatch: The generic method valueOf(Class, String) of type Enum is not applicable for the arguments (Class, String). The inferred type Foo is not a valid substitute for the bounded parameter > – jilt3d Mar 16 '14 at 13:21

1 Answers1

3

Just iterate over all your enum types (you can place them in some collection) and invoke

Enum.valueOf(EnumType, nameOfConstant) 

But be careful because this method will throw java.lang.IllegalArgumentException if checked enum type will not have described field.


Other way would be just iterating over collection of your enum types, getting its values (you can use Class.getEnumConstants here) and checking if name of enum constant is same as name passed by user.

public static Enum<?> getEnumFromMany(Collection<Class<?>> enums, String value) {
    for (Class<?> enumType : (Collection<Class<?>>) enums)
        if (enumType.isEnum()) {
            @SuppressWarnings("unchecked")
            Class<Enum<?>> clazz = (Class<Enum<?>>) enumType;
            for (Enum<?> en : clazz.getEnumConstants()) {
                if (en.name().equals(value))
                    return en;
            }
        }
    return null;
}

I used Collection> instead of Collection<Class<Enum<?>>> because generics are not covariant so Collection<Class<Enum<?>>> would not let you add Class<YourEnum>> to itself.

Usage example

public enum DaysILike {
    FRIDAY, SATURDAY, SUNDAY
}

public enum DaysIDontLike {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY
}

public static void main(String[] args) {
    List<Class<?>> enums = new ArrayList<>();
    enums.add(DaysIDontLike.class);
    enums.add(DaysILike.class);
    String yourString = "THURSDAY";
    Enum<?> en = getEnumFromMany(enums, yourString);
    System.out.println(en + " from " + en.getClass());
}

Output: THURSDAY from class SO22436944$DaysIDontLike

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • This seems to do the trick, thanks. However, I am concerned about the suppressing of the unchecked warning. According to the current implementation we can add an enum type to the `enums` collection which enum type may implement `Bar` interface, for example (without getting compile error). So the question is how to express, in terms of Java Generics, the class type of an enum which extends Foo interface? I've tried several things but I failed to get it right... – jilt3d Mar 16 '14 at 14:35
  • Actually I don't know what to suggest you because I don't know how you are going to use this method. You are saying that problem with this code is that it lets adding enum types which do not implement `Foo` interface, but if you are the person who is creating collections of classes which should be iterated in this method then you should be able to add only correct enum classes. If you want you can add additional test to check if class literal represents also `Foo` type by adding `&& Foo.class.isAssignableFrom(fooType)` to `if (fooType.isEnum()` condition because generics cant be fully trusted. – Pshemo Mar 16 '14 at 18:07
  • @jilt3d You can also consider using list of classes of `? extends Foo` type only, and internally checking if class is also enum like it is done now. – Pshemo Mar 16 '14 at 18:09
  • The method is in a framework code so I want to enforce the client of the method to pass only enum types which extend `Foo` interface. Of course, I can include those checks in the method's body and throw exception when false but I want to enforce that on compiler level. – jilt3d Mar 16 '14 at 20:22
  • Only way I know to specify it in compilation time is using something similar to `T extends Enum && Foo` like in this example `public static & Foo> Foo getEnumFromMany(Collection> foos, String value) {`, but while this seems to be correct approach it is not, because `T` in usage can represent single type which fulfils this conditions, not family of types. Unfortunately in Java we can't use multiple type wildcard `? extends Enum> && Foo` which would solve this problem. – Pshemo Mar 16 '14 at 20:57
  • Normally this problem could be solved by creating one base class which implements `Foo` and derive all other classes from it, but in case of enums they are final so you can't extend them. – Pshemo Mar 16 '14 at 21:08
  • "Checkmate" - Java said. – jilt3d Mar 17 '14 at 09:05