using enum constants obtained through reflection
I have a program which obtains an Enum value via reflection (any Enum is allowed), and I want to do something with it by wrapping it in a generic class that takes an Enum as its type parameter. I am not sure how to properly call the constructor, however. The only way I can get it to work is to use a raw type.
(clarification: My real program is complicated, and looks up the enum classname from a user-provided file at runtime. My real program's wrapper class has additional state and methods that cannot be accomplished with an enum, so I'm not just doing this for academic sake. I wrote the example program below to illustrate the issue. It may look contrived, but it's supposed to be for illustrative purposes.)
Can anyone help me fix the line
EnumWrapper<?> ewrapped = new EnumWrapper(e);
below so it has a less evil warning?
The program works as expected (prints out stack traces of 3 caught exceptions for enum constants not found, otherwise prints lists of wrapped enums), but I make it a habit never to use raw types, and don't know how to fix this case.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GenericEnum2 {
enum Bird { OWL, EAGLE, HAWK };
enum Mammal { LION, TIGER, BEAR };
static class EnumWrapper<E extends Enum<E>>
{
final private E value;
public EnumWrapper(E value) { this.value = value; }
public E getEnum() { return this.value; }
@Override public String toString() { return "wrapped "+value.toString(); }
static public <E extends Enum<E>> EnumWrapper<E> wrap(E e) {
return new EnumWrapper<E>(e);
}
}
public static void main(String[] args) {
List<EnumWrapper<?>> list = new ArrayList<EnumWrapper<?>>();
list.add(EnumWrapper.wrap(Bird.OWL));
list.add(EnumWrapper.wrap(Bird.EAGLE));
list.add(EnumWrapper.wrap(Bird.HAWK));
list.add(EnumWrapper.wrap(Mammal.LION));
list.add(EnumWrapper.wrap(Mammal.TIGER));
list.add(EnumWrapper.wrap(Mammal.BEAR));
System.out.println(list);
list.clear();
for (String s : Arrays.asList(
"Bird.OWL",
"Bird.HAWK",
"Bird.FULVOUS_WHISTLING_DUCK",
"Mammal.LION",
"Mammal.BEAR",
"Mammal.WARTHOG",
"Computer.COMMODORE_64"
))
{
String className = GenericEnum2.class.getCanonicalName()+"$"+s;
try
{
Enum<?> e = getEnum(className);
// EnumWrapper<?> ewrapped0 = EnumWrapper.wrap(e);
/*
* Bound mismatch: The generic method wrap(E) of type
* GenericEnum2.EnumWrapper<E> is not applicable for
* the arguments (Enum<capture#2-of ?>). The inferred
* type Enum<capture#2-of ?> is not a valid substitute for
* the bounded parameter <E extends Enum<E>>
*/
// EnumWrapper<?> ewrapped0 = new EnumWrapper<?>(e);
// Cannot instantiate the type GenericEnum2.EnumWrapper<?>
EnumWrapper<?> ewrapped = new EnumWrapper(e);
// this works but gives me the warning of "EnumWrapper" being a raw type
list.add(ewrapped);
}
catch (IllegalArgumentException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
System.out.println(list);
}
static public Enum<?> getEnum(String enumFullName) throws IllegalArgumentException, ClassNotFoundException
{
String[] x = enumFullName开发者_JAVA百科.split("\\.(?=[^\\.]+$)");
if (x.length == 2)
{
String enumClassName = x[0];
String enumName = x[1];
@SuppressWarnings("unchecked")
final Class<Enum> cl = (Class<Enum>)Class.forName(enumClassName);
if (cl.isEnum())
{
@SuppressWarnings("unchecked")
final Enum result = Enum.valueOf(cl, enumName);
return result;
}
else
throw new IllegalArgumentException("Class is not an enum: "+enumClassName);
}
return null;
}
}
edit: updated getEnum() per OrangeDog's suggestions:
static public Enum<?> getEnum(String enumFullName) throws IllegalArgumentException, ClassNotFoundException
{
String[] x = enumFullName.split("\\.(?=[^\\.]+$)");
if (x.length == 2)
{
String enumClassName = x[0];
String enumName = x[1];
final Class<?> cl = Class.forName(enumClassName);
if (cl.isEnum())
{
for (Object o : cl.getEnumConstants())
{
Enum<?> e = (Enum<?>)o;
if (enumName.equals(e.name()))
return e;
}
}
else
throw new IllegalArgumentException("Class is not an enum: "+enumClassName);
}
return null;
}
}
When mixing generics and dynamics there usually isn't a way to make everything syntactically type-safe.
You can usually reduce it to a single use of @SuppressWarnings("unchecked")
or @SuppressWarnings("rawtypes")
which you can then prove to be correct using good old-fashioned logical reasoning. The decision you have to make is where you put the "unsafe" operation.
On specifics though, Class<Enum>
is wrong. You want to use Class<? extends Enum>
(or just Class<?>
as you're doing the isEnum()
check anyway).
Also, you can more type-safely reconstruct the instance using Class<?>.getEnumConstants()
. Either iterate through looking for the correct name, or index directly using the ordinal.
For wrapping the enum-object we need the actual type of the enum, like in your wrap-method (it is enough to have this as a type parameter). But for this the compiler needs to infer the type, and it seems this is not possible in this case.
The problem is that while each instance of Enum<E>
is in fact of type E
, Enum<E>
is not actually compatible to E
for the compiler. A following of this is that <E extends Enum>
and <E extends Enum<E>>
are not compatible type variable declarations, thus we can't infer the parameter of
<E extends Enum<E>> E getInstance(Class<E> c, String name)
when called with an argument of type Class<?>
.
That said, I found a way where only one unchecked warning occurs, and this is a clearly obviously false one:
public class GenericEnum2 {
...
public static void main(String[] args) {
...
for(...) {
...
try
{
Enum<?> e = getEnum(className);
EnumWrapper<?> wrapper = makeWrapper(e);
list.add(wrapper);
}
...
}
...
}
/**
* casts an instance of a Enum to its right type.
*/
static <E extends Enum<E>> E cast(Enum<E> e) {
// @SuppressWarning("unchecked")
E result = (E)e;
return result;
}
/**
* makes a wrapper for an enum instance.
* @see EnumWrapper#wrap
*/
static <E extends Enum<E>> EnumWrapper<E> makeWrapper(Enum<E> e) {
return EnumWrapper.wrap(cast(e));
}
...
}
Directly putting EnumWrapper.wrap(cast(e))
in the main method did not work - there javac was not able to infer that wrap would accept the same same type as cast returned.
It might be possible to avoid some of the SuppressWarnings in your getEnum method by splitting it to several methods and making similar casts, but I didn't really try.
精彩评论