我有一个通过反射获取Enum值的程序(允许任何Enum),我想通过将它包装在以Enum作为其类型参数的泛型类中来做它。但是,我不确定如何正确调用构造函数。我可以让它工作的唯一方法是使用原始类型。
(澄清:我的真实程序很复杂,并且在运行时从用户提供的文件中查找枚举类名。我的真实程序的包装类具有其他状态和方法无法通过enum,所以我不只是为了学术而这样做。我在下面编写了示例程序来说明问题。它可能看起来很人为,但它应该用于说明目的。)
任何人都可以帮我解决问题吗
EnumWrapper<?> ewrapped = new EnumWrapper(e);
以下它有一个不那么邪恶的警告?
该程序按预期工作(打印出未找到枚举常量的3个捕获异常的堆栈跟踪,否则打印包装枚举的列表),但我习惯永远不要使用原始类型,并且不知道如何解决这个问题。
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.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;
}
}
编辑:根据OrangeDog的建议更新了getEnum():
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;
}
}
答案 0 :(得分:4)
当混合泛型和动态时,通常无法使语法上的所有内容都安全。
您通常可以将其简化为@SuppressWarnings("unchecked")
或@SuppressWarnings("rawtypes")
,然后使用良好的老式逻辑推理证明您是正确的。您必须做出的决定就是放置“不安全”操作。
但具体而言,Class<Enum>
是错误的。您希望在执行Class<? extends Enum>
检查时使用Class<?>
(或仅isEnum()
。)
此外,您可以使用Class<?>.getEnumConstants()
更安全地重新类型化实例。要么通过查找正确的名称来迭代,要么使用序数直接索引。
答案 1 :(得分:1)
为了包装枚举对象,我们需要实际的枚举类型,就像在wrap-method中一样(将它作为类型参数就足够了)。但为此,编译器需要推断出类型,在这种情况下似乎不可能。
问题是虽然Enum<E>
的每个实例实际上都是E
类型,但Enum<E>
实际上与编译器的E
不兼容。以下是<E extends Enum>
和<E extends Enum<E>>
不兼容的类型变量声明,因此我们无法推断
<E extends Enum<E>> E getInstance(Class<E> c, String name)
使用Class<?>
类型的参数调用时。
那就是说,我找到了一种只发生一次未经检查的警告的方法,这显然是错误的:
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));
}
...
}
直接将EnumWrapper.wrap(cast(e))
放在main方法中不起作用 - javac无法推断换行将接受与返回的转换相同的类型。
有可能通过将getEnum方法中的一些SuppressWarning分成几个方法并进行类似的转换来避免它们,但我并没有真正尝试过。