Java collections covariance problem
Lets say we have a program which contains such classes:
public interface AbstractItem {
}
public SharpItem implements AbstractItem {
}
public BluntIte开发者_如何学JAVAm implements AbstractItem {
}
public interface AbstractToolbox {
//well the problem starts here...
public List<AbstractItem> getItems();
}
public ExpensiveToolbox implements AbstractToolbox {
private List<SharpItem> items = new ArrayList()<SharpItems>;
public List<SharpItem> getItems() { return this.items; }
}
public CheapTooblox implements AbstractToolbox {
private List<BluntItem> items = new ArrayList()<BluntItem>;
public List<BluntItem> getItems() { return this.items; }
}
Easy, right? Well lets say we now want to make a method like this (in some random class):
public void doImportantStuff(AbstractToolbox toolbox) {
//important stuff!
//this obviously won't work
List<AbstractToolbox> items = toolbox.getItems();
//do some stuffwith all items
}
Now the problem is that in Java collections with generics aren't covariant (hope that's the term I'm looking for) and I can't assign an ArrayList<ExpensiveToolbox>
to a List<AbstractToolbox>
. The only solution I can see here is to duplicate the code and do a version for each type, but that would obviously suck (what if we had more classes implementing AbstractToolbox with different lists?). Oh obviously the second solution would be to drop the generics and make a normal List, but is it a good practice?
Are there any design pattern/practices to tackle such problems?
@Edit: ok so I might not be precise enough. I want all the classes which extend AbstractToolbox to have a List of certain classes which extend AbstractItem and then I want a method that will take an AbstractToolbox as a parameter and do something on the items in its list (using the classes that would be defined in AbstractItem so all the items of every possible list would actually have them).
You're probably going to need to take a look at using wildcard types for generics. Here's a quick link: What is PECS (Producer Extends Consumer Super)?
Quick answer: change the type to List<? extends AbstractItem>
Why can't you just assign this?
Imagine the code here...
List<AbstractItem> foo = new ArrayList<SharpItem>();
foo.add(new BluntItem());
The static typing says this should work... but you can't do that! It would violate the ArrayList's type. That's why this is disallowed. If you change it to
List<? extends AbstractItem> foo = new ArrayList<SharpItem>();
you can then do the assignment, but never add anything to the list. You can still retrieve elements from the list, however, as AbstractItems.
Is just using List (bare type) a good solution?
No, definitely not :-p
Here are a couple of extra ideas. Leave everything the same, but use this:
interface AbstractToolbox {
public List<? extends AbstractItem> getItems();
}
This basically says that the abstract class' items are an unknown type, but subclasses can make it concrete. This would require you to call getItems()
on a reference of type ExpensiveToolbox or CheapToolbox to be able to retrieve a list that allows you to add items, etc.
ExpensiveToolbox toolbox = new ExpensiveToolbox();
AbstractToolbox absTB = toolbox;
List<? extends AbstractItem> items1 = absTB.getItems(); //fine
List<SharpItem> items2 = absTB.getItems(); //compile error
List<SharpItem> items3= toolbox.getItems(); //fine
Alternatively, you could just type AbstractToolbox:
public interface AbstractToolbox<T extends AbstractItem> {
public List<T> getItems();
}
public ExpensiveToolbox implements AbstractToolbox<SharpItem> {
public List<SharpItem> getItems() { //...
}
public interface AbstractItem
{
}
public class SharpItem implements AbstractItem
{
}
public class BluntItem implements AbstractItem
{
}
public interface AbstractToolbox<T extends AbstractItem>
{
public List<T> getItems();
}
public class ExpensiveToolbox implements AbstractToolbox<SharpItem>
{
private List<SharpItem> items = new ArrayList<SharpItem>();
public List<SharpItem> getItems() { return this.items; }
}
public class CheapToolbox implements AbstractToolbox<BluntItem>
{
private List<BluntItem> items = new ArrayList<BluntItem>();
public List<BluntItem> getItems() { return this.items; }
}
public void doImportantStuff(AbstractToolbox<?> toolbox)
{
List<? extends AbstractItem> items = toolbox.getItems();
for(AbstractItem item : items)
... ;
}
精彩评论