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
AsyncTaskcode and changingdoInBackgroundto not use a varargs param because I currently may not need one. I prefer not to do this so I get the benefits whenAsyncTaskis 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.