关于将泛型与varargs相结合的问题有很多问题。这将需要在实际代码尝试实例化它们时不存在的通用数组。此外,还有大量关于编译器的文档 - varargs方法的警告模糊性与不可恢复的参数。由于类型擦除,这会产生潜在的堆污染,因此会出现警告(在调用者的Java 6中)。但是,我的问题不是这些问题本身。我想我明白有些事情是不可能的。 我想知道的是在复杂的情况下优雅地解决这些问题的方法。
相关主题的链接:
我有一个BookItemSearchAddTask
,它扩展自 Android AsyncTask
,但它的继承层次结构中的某个地方已经变得通用,在更高层次上更抽象:
在更高级别, SearchAddTask
,其中包含执行任务的方法 start()
,从知道它的客户端调用传递BookItem
产品。
public abstract class SearchAddTask<ProductToAdd extends Product & NamedProduct>
extends AddTask<ProductToAdd, ProductToAdd> {
public void start(ViewActivity context, ProductToAdd product) throws SpecificAddTaskDomainException, TaskExistsException, TaskUnavailableException {
super.start(context, product);
//more stuff ...
execute(product);
}
}
等级低于 ItemSearchAddTask
。这里按照doInBackground
API的要求实施了 AsyncTask
方法。它仍然可以使用泛型。
public abstract class ItemSearchAddTask extends SearchAddTask<I> {
public I doInBackground(I... params) {
I product = params[0];
//do stuff ...
return product;
}
}
最后 BookItemSearchAddTask
是ItemSearchAddTask<BookItem>
。 BookItem
因此是Item
,是Product
。 “I
”链接到此嵌套任务类ItemSearchAddTask
所在的类:
public abstract class ItemSearchAddWindow<I extends Item & ImageRepresentedProduct & NamedProduct> extends ViewActivity implements View.OnClickListener,
AdapterView.OnItemClickListener {}
现在,当我运行此代码时,我收到以下错误:
Caused by: java.lang.ClassCastException: [Lnet.lp.collectionista.domain.Product;
at net.lp.collectionista.ui.activities.items.book.ItemSearchAddWindow$ItemSearchAddTask.doInBackground(ItemSearchAddWindow.java:1)
请注意“[L
”。
我还在“execute(product);
”获得编译时警告:“Type safety: A generic array of ProductToAdd is created for a varargs parameter
”
据我了解,JVM发现在doInBackground
vararg中,传入了Product[]
,而不是Item[]
(I[]
) 。除了很难想到的通用数组之外,我认为正在发生的事情是http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html处的狮子笼的情况。不是我的代码,而是因为生成的ProductToAdd
通用数组(基本上扩展Product
)是为varargs参数创建的。
我检查过如果我将无参数传递给execute
,那就可以了。另外使用“execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});
”主要是工作(不要问,MusicCDItem
是Item
,就像BookItem
一样。
然而在start()
我无法知道我需要传递BookItem
。 我只知道“I
”。由于这是一个通用的,我无法创建作为varargs参数传递所需的通用数组。我认为我的案例的复杂程度是使用泛型的不同层次,以及并行层次结构。
如何解决此功能差距?我考虑过:
start()
的方法签名),直到我们到达客户端代码,并且只有我通过仅一个产品元素,该元素属于实际类型BookItem
。我会在那里得到相同的警告,但编译器将能够生成正确的通用数组。 AsyncTask
代码并将doInBackground
更改为不使用varargs参数,因为我目前可能不需要。我不想这样做,所以我在将来更新AsyncTask
时会获得好处。start()
中有一些反射代码。难看。有什么更短,更本地的吗?
这个,或者我的代码中有一些非常愚蠢的错误。
答案 0 :(得分:1)
您注意到SearchAddTask.start()
调用execute()
时会收到“未经检查的通用数组”警告。但是,实际警告有点误导。它说的是为varargs参数创建了ProductToAdd的通用数组,但它的真正含义是, ProductToAdd是一个类型变量,在运行时我无法创建数组那些,所以我只能用我最好的猜测。
如果您在调试器中逐步进入execute()
,您会看到为P...
声明创建的数组是Product[1]
- 正是您所期望的从你得到的类演员例外。在运行时,这是JVM可以做的最好的,因为它是ProductToAdd
最接近的未擦除的祖先。
不幸的是,在ItemSearchAddTask
中,JVM也做得最好,它将I...
声明转换为Item[]
(I
最近的未擦除祖先});因此ClassCastException
execute()
试图调用doInBackground()
。
我能想到的最不可思议的方法就是通过在运行时保持ProductToAdd
的具体类并自己创建args数组(正确类型)来回避Java的类型擦除:
abstract class SearchAddTask<ProductToAdd extends Product & NamedProduct>
extends AddTask<ProductToAdd, ProductToAdd> {
private final Class<ProductToAdd> productClass;
SearchAddTask(Class<ProductToAdd> productClass) {
this.productClass = productClass;
}
public void start(ViewActivity context, ProductToAdd product) {
super.start(context, product);
ProductToAdd[] argsArray = (ProductToAdd[]) Array.newInstance( productClass, 1 );
argsArray[0] = product;
execute( argsArray );
}
}
这确实意味着您必须确保传递BookItem.class
,可能是在您创建AddWindow<BookItem>
时,但它会保持丑陋。