1

I have the following code

public interface SomeInterface {    }


public class TestConstants {
    public static <E extends Enum<E> & SomeInterface> E getEnumString(Class<E> clazz, String s){
        for(E en : EnumSet.allOf(clazz)){
            if(en.name().equalsIgnoreCase(s)){
                return en;
            }
        }
        return null;
    }
    public enum A implements SomeInterface {
        V1,
        V2,
        V3;
    }

    public enum B implements SomeInterface {
        V1,
        V2,
        V3;
    }
    // And so on.......
}

Elsewhere in the code I can call call

TestConstants.getEnumString(TestConstants.B.Class,"V2")

and it will return TestConstants.B.V2 All is good.

But my question is what if I don't know the actual Enum class I will search i.e.

Class<?> enumClass contains one of the Enums, but I don't know which. Now I from code that is external to the TestConstants class, I could use something like the following

if (enumClass.equals(TestConstants.A.class)) {
    TestConstants.getEnumString(TestConstants.A.class,value);
} else if (enumClass.equals(TestConstants.B.class)) {
    TestConstants.getEnumString(TestConstants.B.class,value);
} else if (enumClass.equals(TestConstants.C.class)) {
// And so on....

But I'd really like to move this code into the TestConstants class. but I don't seem to be able to. If I add this function for example

public static <E extends Enum<E> & SomeInterface> E fromUnknownClass(Class<?> enumClass, String name) {
    if (enumClass.equals(TestConstants.A.class)) {
       return TestConstants.fromString(TestConstants.A.class,name); <-- Compile error
    } else if .......
        .......
}

I get the following compile error

incompatible types: inference variable E has incompatible bounds equality constraints: 
TestConstants.A upper bounds: E,java.lang.Enum<E>,SomeInterface

Is what I'm trying for here even possible, or is there some Generics magic that I'm not aware of?

muttonUp
  • 6,351
  • 2
  • 42
  • 54
  • You return e.name()... the string representation of the enum....don't you want the enum? Or what sense does it make that you put "v2" in your parameters, just to get back "v2"...? – ElDuderino Feb 27 '15 at 00:22
  • I dont understand your problem. So either explain it better or simple in 'your code external to TestConstants' you just call TestConstants.getEnumString(enumClass,value); You dont have to provide class name 'statically ' – Zielu Feb 27 '15 at 00:44
  • Have you tried casting the `Class>` argument to `Class & SomeInterface>`? – fps Feb 27 '15 at 00:51
  • @Zielu TestConstants.getEnumString(enumClass,value); won't compile as it requires Class but you are giving it Class> – muttonUp Feb 27 '15 at 00:51
  • But why is it declared Class> not Enum> if you know that you want to represent enum. Can you post the code for which you actually need it and why you are not simple calling valeof on the Enum but implement it yourself. – Zielu Feb 27 '15 at 00:57
  • @Magnamag I haven't but I'm not sure what that would achieve, as the problem isn't with enumClass, but the subsequent call once I have identified enumClass. – muttonUp Feb 27 '15 at 00:57
  • 1
    Maybe this is what you need? http://stackoverflow.com/q/15978730/2001247 – ElDuderino Feb 27 '15 at 00:58
  • @ElDuderino perfect! Thats solved it. Even better I don't need the if else if loop. – muttonUp Feb 27 '15 at 01:04
  • 1
    @ElDuderino posted the question that has the answer you need. You have to cast the argument. But beware of the warning that's in the answer. – fps Feb 27 '15 at 01:07
  • Yes it would seem I am falling foul of that warning. Caller is able to specify E. I shall have to think on this more. Thanks @Magnamag – muttonUp Feb 27 '15 at 01:21

1 Answers1

3

You are making your life unnecessarily hard. In your generic signature

public static <E extends Enum<E> & SomeInterface> E getEnumString(Class<E> clazz, String s)

The declaration of SomeInterface is obsolete. After all, the method works regardless of whether the particular enum implements SomeInterface. It guarantees that the returned type matches the class that you pass in (read: is of type E) therefore the result will implement SomeInterface if E implements SomeInterface.

So if you change the method to

public static <E extends Enum<E>> E getEnumString(Class<E> clazz, String s)

and pass in A.class or B.class the result will be of type A or B, respectively, both implementing SomeInterface. This even works in a generic context where the caller might indeed need the constraint the SomeInterface is implemented:

public <T extends Enum<T>&SomeInterface> void doSomethingWithSomeInterface(Class<T> cl) {
    // works without getEnumString declaring SomeInterface ...
    T t=getEnumString(cl, "V1");
}

However, if you pass in a Class<?>, nobody can say whether the result will implement SomeInterface and the best way to verify it is to do an ordinary cast or instanceof on the result.

If you have changed the signature as described above, it’s easy to use, even with unknown types:

Class<?> cl=A.class; // the "unknown" type Class<?>
Object obj=getEnumString(cl.asSubclass(Enum.class), "V1");

Note that if you don’t need to match case insensitive, there are easier ways to get an enum constant. For a known type you may simply call, e.g. A.valueOf("V1"), and for a generic method, it is as simple as:

public static <E extends Enum<E>> E getEnumString(Class<E> clazz, String s){
    return Enum.valueOf(clazz, s);
}

which renders the entire utility method obsolete as Enum.valueOf(Class, String) already is that utility method.

Even if you need the case insensitive lookup you may consider that enum constants are usually all-uppercase and if you adhere to this convention, the entire operation becomes:

Class<?> clazz=A.class; // simulate unknow type
String name="v1"; // for case insensitive match
// the operation:
Object aV1=Enum.valueOf(clazz.asSubclass(Enum.class), name.toUpperCase(Locale.ROOT));
SomeInterface si=(SomeInterface)aV1;

without the need for additional utility methods.

Holger
  • 285,553
  • 42
  • 434
  • 765