views:

115

answers:

4

Lets say we have a program which contains such classes:

public interface AbstractItem {
}
public SharpItem implements AbstractItem {
}
public BluntItem 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).

A: 

No covariance in Java.

Make those Lists of AbstractItem and implement the method in CheapToolbox to only add BluntItems; implement the method in ExpensiveToolbox to only add SharpItems.

public interface AbstractItem {
}
public SharpItem implements AbstractItem {
}
public BluntItem implements AbstractItem {
}

public interface AbstractToolbox {
}
public ExpensiveToolbox implements AbstractToolbox {
    private List<AbstractItem> items = new ArrayList()<AbstractItem>;
    public List<AbstrarctItem> getItems() { return this.items; }
}
public CheapTooblox implements AbstractToolbox {
    private List<AbstractItem> items = new ArrayList()<AbstractItem>;
    public List<AbstractItem> getItems() { return this.items; }
}
duffymo
+8  A: 

You're probably going to need to take a look at using wildcard types for generics. Here's a quick link: http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs

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

Steven Schlansker
Or, alternatively, type AbstractToolbox with a type parameter <T extends AbstractItem> and use that instead.
Mark Peters
But then when you accept an AbstractToolbox you either need to deal with the parameter T or wildcard *that*. If you only ever have to deal with AbstractItems, this has the nice property that you don't have to add the type parameter to other parts of the code.
Steven Schlansker
Love it, thanks!
Zenzen
@Steven: See the first part of my response regarding that. Some of the misunderstanding is caused by the original question being completely wrong. In the post, the first part doesn't compile, but given AbstractToolbox's implementation, the latter part would compile fine which he says "obviously won't work." Another case of careless example code.
Mark Peters
Ach, sorry, I got confused by the conflicting information. Your example code makes it much clearer :)
Steven Schlansker
+4  A: 

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() { //...
}
Mark Peters
+1 for typing AbstractToolbox, I think this is the obvious solution.
matt b
A: 
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) 
        ... ;

}
irreputable