Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java collections covariance problem

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

like image 932
Mateusz Dymczyk Avatar asked Sep 21 '10 18:09

Mateusz Dymczyk


People also ask

Does Java support covariance?

In Java, arrays are covariant, which has 2 implications. Firstly, an array of type T[] may contain elements of type T and its subtypes. Secondly, an array of type S[] is a subtype of T[] if S is a subtype of T .

Why are generics not covariant in Java?

Because ln is a List, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.

Why are arrays covariant in Java?

Arrays are said to be covariant which basically means that, given the subtyping rules of Java, an array of type T[] may contain elements of type T or any subtype of T . For instance: Number[] numbers = newNumber[3]; numbers[0] = newInteger(10); numbers[1] = newDouble(3.14); numbers[2] = newByte(0);

What is Contravariance Java?

Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.


2 Answers

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

like image 186
Steven Schlansker Avatar answered Sep 23 '22 15:09

Steven Schlansker


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() { //...
}
like image 31
Mark Peters Avatar answered Sep 21 '22 15:09

Mark Peters