我有一个简单的通用列表类。我试图实现这一点:给定一个列表实例,我们只知道它包含一个类的特定子类的实例,并给定该类的实例,如果它是包含(子类)类型的实例,则将此实例添加到列表中,否则抛出异常(例如。ClassCastException)。我尝试了以下操作:
class MyList<T>
{
private Class<T> genericClass;
private List<T> list = new ArrayList<>();
public MyList(Class<T> genericClass)
{
this.genericClass = genericClass;
}
public void add(T elem)
{
list.add(elem);
}
//...
public Class<T> getGenericParamClass()
{
return genericClass;
}
}
class A{}
class B extends A{}
class C extends A{}
class Program
{
public static void main(String... args)
{
MyList<B> list1 = new MyList<>(B.class);
MyList<C> list2 = new MyList<>(C.class);
MyList<? extends A> ls = checkStuff() ? list1 : list2;
ls.add(ls.getGenericParamClass().cast(lotsOfStuff())); //ERROR ?!!
}
static boolean checkStuff()
{
Random random = new Random();
return random.nextBoolean();
}
static A lotsOfStuff()
{
return new B();
}
}
我认为给定一个Class对象,其类型参数与方法的参数类型相同,我将能够使用前者强制转换某些内容,以便能够将其传递给后者。唉,似乎我不能:我得到一个编译时错误!
我可以把泛型扔到窗外,完全不检查,只说:
A val = lotsOfStuff();
if (myList.getGenericParamClass().isInstance(val))
ls.add(val)
else
throw new SomeException();
但是,这可能会产生比它解决的更多的问题,而且它真的会让我烦恼。
我在这里错过了什么,或者只是不可能像我想的那样?
编辑:我完全理解为什么这样的事情不能工作:
List<? extends Number> abc=new ArrayList<Integer>(); abc.add(new Integer(10));
但在我看来,以下传递性是有效的:add()的参数类型是相同的,MyList的类型参数是相同的,getGenericParamClass()返回的类的类型参数是相同的,该类的cast()方法的返回类型是相同的。我(作为人类)可以知道这些未知类型是相同的,因为我从同一个对象获得它们。
是我的逻辑有问题,还是这是Java的限制?
编译错误为:
方法add(capture#2-of ?类型MyList中的extends A)不适用于参数(捕获#3-of ?扩展)
要理解该消息,请记住,? extends A
表示未知类型,该类型是A
的子类型。该编译器无法知道lotsOfStuff()
返回的? extends A
与MyList.add
方法期望的? extends A
相同(或其子类型),事实上,您的程序并不能确保这种情况(因为lotsOfStuff()
总是返回B
,即使它应该添加到C
列表中。)
要表示两者是同一类型,必须使用类型形参。最简单的方法是将进行类型转换和抛出的代码移到类MyList<T>
中(该类已经具有合适的类型参数),例如通过添加以下方法:
void addOrThrow(Object o) {
add(genericClass.cast(o));
}