解决无法将不可具体化参数与 varargs 一起使用的问题
关于泛型与可变参数相结合的问题存在很多疑问。这将需要通用数组,当实际代码尝试实例化它们时,这些数组并不存在。此外,还有大量关于来自具有不可具体化参数的 varargs 方法的警告的编译器模糊性的文档。由于类型擦除,这会造成潜在的堆污染,因此会出现警告(在调用方的 Java 6 中)。然而,我的问题并不是关于这些问题本身。我想我明白有些事情是不可能的。 我想知道的是在我的复杂案例中优雅地解决这些问题的方法。
相关主题的链接:
- 是否可以解决“为 a 创建 T 的通用数组” varargs 参数”编译器警告? 有些人称这种情况为“不良功能”错误。
- http://docs.oracle.com/ javase/tutorial/java/generics/non-reifying-varargs-type.html
我的情况是,
我有一个从 Android 扩展的 BookItemSearchAddTask
AsyncTask
但其继承层次结构中的某个地方已经变得通用,在更高级别上更加抽象:
在更高级别上它是 SearchAddTask
, 方法从知道传入 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);
}
}
其中包含用于执行任务的 start()
方法,该 这是一个ItemSearchAddTask
。这里按照 AsyncTask
API 的要求实现了 doInBackground
方法。它仍然可以使用泛型。
public abstract class ItemSearchAddTask extends SearchAddTask<I> {
public I doInBackground(I... params) {
I product = params[0];
//do stuff ...
return product;
}
}
最后BookItemSearchAddTask
是ItemSearchAddTask
。因此,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);
”处收到编译时警告:“类型安全:为 varargs 参数创建了 ProductToAdd 的通用数组
”
原因
据我了解,JVM 发现在 doInBackground
vararg 中它获取了传入的 Product[]
,而不是 Item[ ]
(I[]
) 它期望。除了难以想象的泛型数组之外,我认为正在发生的事情是狮子笼的情况 http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html。不是通过我的代码,而是因为为 varargs 参数创建了 生成的 ProductToAdd
通用数组(它基本上扩展了 Product
)。
我检查过,如果我没有参数传递给execute
,那么它就可以工作。还使用“execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});
”大部分工作(不要问,MusicCDItem
是一个< code>Item,就像 BookItem
一样)。
解决方案?
但是在 start()
中,我不知道我需要传入 BookItem
。 我只知道“I
”。由于这是一个泛型,我无法创建作为 varargs 参数传递所需的泛型数组。我认为我的案例的复杂之处在于使用泛型的不同级别以及并行层次结构。
如何解决这个功能差距?我考虑过:
- 删除仿制药。有点激烈。
- 在所有泛型代码位中始终保留可变参数参数(即更改
start()
的方法签名),直到我们到达客户端代码,并且只有在那里才可以我仅传递一个产品元素,该元素属于真实类型BookItem
。我会在那里得到相同的警告,但编译器将能够生成正确的通用数组。 - 复制
AsyncTask
代码并更改doInBackground
以不使用 varargs 参数,因为我目前可能不需要它。我不想这样做,这样当将来更新AsyncTask
时我就能受益。 - 也许
start()
中有一些反射代码。丑陋的。
有没有更短、更本地化的内容?
要么是这个,要么是我的代码中有一些非常愚蠢的拼写错误。
There are many questions about the issue of combining generics with varargs. This would require generic arrays which don't exist when actual code tries to instantiate them. Moreover, there's a good amount of documentation on the compiler-vagueness of warnings from varargs methods with non-reifiable parameters. Because of type erasure this creates potential heap pollution, hence the warning (in Java 6 at the caller). However, my question is not about these problems themselves. I think I understand that some things aren't possible. What I'd like to know is the way to elegantly workaround these problems in my complex case.
Links for related topics:
- Is it possible to solve the "A generic array of T is created for a varargs parameter" compiler warning? where some call this situation a "bad feature" bug.
- http://docs.oracle.com/javase/tutorial/java/generics/non-reifiable-varargs-type.html
My case
I have a BookItemSearchAddTask
that extends from the Android AsyncTask
but somewhere along its inheritance hierarchy has been made generic, more abstract at higher levels:
At a higher level it's SearchAddTask
, which contains the method start()
to execute the task, called from a client that knows that it passes a BookItem
product in.
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);
}
}
A level lower it's an ItemSearchAddTask
. Here the method doInBackground
is implemented, as required by the AsyncTask
API. It can still use generics.
public abstract class ItemSearchAddTask extends SearchAddTask<I> {
public I doInBackground(I... params) {
I product = params[0];
//do stuff ...
return product;
}
}
Finally BookItemSearchAddTask
is ItemSearchAddTask<BookItem>
. A BookItem
therefore is an Item
, is a Product
. The "I
" is linked to the class in which this nested task class, ItemSearchAddTask
, finds itself:
public abstract class ItemSearchAddWindow<I extends Item & ImageRepresentedProduct & NamedProduct> extends ViewActivity implements View.OnClickListener,
AdapterView.OnItemClickListener {}
The problem
Now, when I run this code I get the following error:
Caused by: java.lang.ClassCastException: [Lnet.lp.collectionista.domain.Product;
at net.lp.collectionista.ui.activities.items.book.ItemSearchAddWindow$ItemSearchAddTask.doInBackground(ItemSearchAddWindow.java:1)
Note the "[L
".
I also get compile time warnings at "execute(product);
": "Type safety: A generic array of ProductToAdd is created for a varargs parameter
"
The cause
To my understanding, the JVM finds that in the doInBackground
vararg it gets a Product[]
passed in, rather than the Item[]
(I[]
) it expects. Apart from the generic arrays, which are hard to think about, I think what's going on is the case of the lion cage at http://docs.oracle.com/javase/tutorial/java/generics/subtyping.html. Not by my code, but because the generated generic array of ProductToAdd
(which basically extends Product
) was created for the varargs param.
I checked that if I pass no argument to execute
, that it works. Also using "execute((ProductToAdd[])new MusicCDItem[]{new MusicCDItem()});
" worked mostly (don't ask, a MusicCDItem
is an Item
, just like a BookItem
is).
A solution?
However in start()
I can't know that I need to pass in a BookItem
. I only know about "I
". Since that is a generic, I can't create the generic array that is required to pass as the varargs param. I think what is complex about my case is the different levels of using generics, as well as the parallel hierarchies.
How do I work around this feature gap? I considered:
- Removing generics. A bit drastic.
- Holding on the varargs param everywhere in all the generics bits of code (i.e. change the method signature of
start()
), until we reach the client code, and only there do I pass one product element only, and that element is of a real type,BookItem
. I will get the same warning there but the compiler will be able to generate the correct generic array. - Duplicating the
AsyncTask
code and changingdoInBackground
to not use a varargs param because I currently may not need one. I prefer not to do this so I get the benefits whenAsyncTask
is updated in the future. - Perhaps some reflection code in
start()
. Ugly.
Is there anything shorter and more local?
Either this, or there is some really stupid typo in my code.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您注意到,当
SearchAddTask.start()
调用execute()
时,您会收到“Unchecked generic array”警告。然而,实际的警告有点误导。它所说的是为可变参数参数创建了 ProductToAdd 的通用数组,但它的真正含义是,ProductToAdd 是一个类型变量,在运行时我无法创建数组其中,我只能使用我的最佳猜测。如果您在调试器中单步执行
execute()
,您将看到为P...
声明是一个Product[1]
——正是您所期望的类转换异常。在运行时,这是 JVM 可以做的最好的事情,因为它是ProductToAdd
最接近的未擦除祖先。不幸的是,在
ItemSearchAddTask
中,JVM 也尽力了,即将I...
声明转换为Item[]
(I
最近的未擦除祖先);因此,当execute()
尝试调用doInBackground()
时,会出现ClassCastException
。我能想到的解决这个问题的最不糟糕的方法是通过在运行时保留 ProductToAdd 的具体类并自己创建 args 数组(正确类型)来回避 Java 的类型擦除:
这确实意味着您必须确保
BookItem.class
被传入,可能是在创建AddWindow
时,但它保留了丑陋的内容。You noted that you get an "Unchecked generic array" warning in
SearchAddTask.start()
when it callsexecute()
. However, the actual warning is slightly misleading. What it says is A generic array of ProductToAdd is created for a varargs parameter, but what it really means is, ProductToAdd is a type variable and at run-time I can't create an array of those, so I'll just have to use my best guess.If you step through into
execute()
in the debugger, you'll see that the array that was created for theP...
declaration is aProduct[1]
-- exactly what you'd expect from the class cast exception you got. At run time, this is the best the JVM can do, because it's the closest un-erased ancestor ofProductToAdd
.Unfortunately, in
ItemSearchAddTask
, the JVM's also done the best it can, which is convert theI...
declaration into anItem[]
(the closest un-erased ancestor ofI
); thus theClassCastException
whenexecute()
tries to calldoInBackground()
.The least awful way I can think of offhand to get around this is to sidestep Java's type erasure by keeping
ProductToAdd
's concrete class around at run time and creating the args array (of the correct type) yourself:It does mean you have to ensure that
BookItem.class
gets passed in, probably when you create theAddWindow<BookItem>
, but it keeps the ugliness contained.