13

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).

Mateusz Dymczyk
  • 14,969
  • 10
  • 59
  • 94
  • To clarify, wouldn't you want your doImportantStuff to have: List items = toolbox.getIems(); – MikeTheReader Sep 21 '10 at 18:21
  • What do you mean, "Easy, right?" Your first section doesn't compile, and shouldn't! – Mark Peters Sep 21 '10 at 18:22
  • 1
    Well I figured it would be easy for SO. I mean obviously *I* am having problems with it ;) – Mateusz Dymczyk Sep 21 '10 at 18:25
  • 1
    It would be good if you spent more care in posting examples. There are loads of syntax and logical errors in your examples which complicate giving you an answer. For instance, whereas the first part doesn't compile and you imply it does, the second part you imply obviously won't compile when it obviously does! – Mark Peters Sep 21 '10 at 18:39
  • 1
    Well the problem is that if I knew how to write an example which compiles and runs (and works as intended!) I wouldn't have a problem since it would compile and run ;) – Mateusz Dymczyk Sep 21 '10 at 19:28
  • @Zenzen: It doesn't have to compile and run, for obvious reasons as you note :-). But typically you hope that it demonstrates the problem you describe (e.g. exhibits the compile error in the same place). – Mark Peters Sep 21 '10 at 19:52
  • I don't see a problem with the "List items = toolbox.getItems();" line in the doImportantStuff method. That looks right to me. – walters Sep 21 '10 at 23:00

3 Answers3

25

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

Community
  • 1
  • 1
Steven Schlansker
  • 37,580
  • 14
  • 81
  • 100
  • 3
    Or, alternatively, type AbstractToolbox with a type parameter and use that instead. – Mark Peters Sep 21 '10 at 18:25
  • 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 Sep 21 '10 at 18:26
  • 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 Sep 21 '10 at 18:36
  • Ach, sorry, I got confused by the conflicting information. Your example code makes it much clearer :) – Steven Schlansker Sep 21 '10 at 19:00
5

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
  • 80,126
  • 17
  • 159
  • 190
0
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
  • 44,725
  • 9
  • 65
  • 93