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.