关于将泛型与varargs组合在一起的问题有很多问题。这将需要在实际代码尝试实例化它们时不存在的通用数组。此外,关于无参数的varargs方法的警告,有很多关于编译器的模糊文档。由于类型擦除会造成潜在的堆污染,因此会产生警告(在Java 6中,调用方)。但是,我的问题不是这些问题本身。我想我知道有些事情是不可能的。我想知道的是,在我的复杂案例中,如何优雅地解决这些问题的方法。
相关主题的链接:
Is it possible to solve the "A generic array of T is created for a varargs parameter" compiler warning?,有人将此情况称为“错误功能”错误。
http://docs.oracle.com/javase/tutorial/java/generics/non-reifiable-varargs-type.html
我的情况
我有一个从Android BookItemSearchAddTask
扩展而来的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
变量中传递了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参数传递所需的泛型数组。我认为我的案例的复杂之处在于使用泛型的不同级别以及并行层次结构。如何解决此功能差距?我考虑过:
删除泛型。有点激烈。
在代码的所有泛型位中到处都保持varargs参数(即更改
start()
的方法签名),直到到达客户代码,并且仅在那里我仅传递一个product元素,并且该元素是真实的类型BookItem
。我在那里会收到相同的警告,但是编译器将能够生成正确的通用数组。复制
AsyncTask
代码并将doInBackground
更改为不使用varargs参数,因为我目前可能不需要。我不想这样做,这样将来AsyncTask
会在更新时得到好处。也许
start()
中有一些反射代码。丑陋。有没有更短更本地的东西?
要么这个,要么我的代码中有一些非常愚蠢的错字。
最佳答案
您注意到,当它调用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>
时传入的,但是它却保留了丑陋感。
关于java - 解决无法将可变参数与varargs结合使用的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8693235/