Проблема ковариации Java-коллекций - PullRequest
8 голосов
/ 21 сентября 2010

Допустим, у нас есть программа, которая содержит такие классы:

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; }
}

Легко, верно?Давайте предположим, что теперь мы хотим создать такой метод (в некотором случайном классе):

public void doImportantStuff(AbstractToolbox toolbox) {
//important stuff!
//this obviously won't work
    List<AbstractToolbox> items = toolbox.getItems();
//do some stuffwith all items
}

Теперь проблема заключается в том, что в Java-коллекциях с обобщениями нет ковариантности (надеюсь, это терминищу) и я не могу присвоить ArrayList<ExpensiveToolbox> List<AbstractToolbox>.Единственное решение, которое я вижу здесь, - это продублировать код и создать версию для каждого типа, но это, очевидно, будет отстой (что если бы у нас было больше классов, реализующих AbstractToolbox с разными списками?).О, очевидно, вторым решением было бы отказаться от дженериков и создать обычный список, но разве это хорошая практика?

Существуют ли какие-либо шаблоны / методы проектирования для решения таких проблем?

@ Edit: ok, так что я не могу быть достаточно точным.Я хочу, чтобы все классы, которые расширяют AbstractToolbox, имели список определенных классов, которые расширяют AbstractItem, а затем мне нужен метод, который будет принимать AbstractToolbox в качестве параметра и делать что-то с элементами в его списке (используя классы, которые будут определены вAbstractItem, чтобы все элементы каждого возможного списка действительно имели их).

Ответы [ 3 ]

21 голосов
/ 21 сентября 2010

Возможно, вам понадобится взглянуть на использование подстановочных типов для обобщений.Вот быстрая ссылка: Что такое PECS (производитель расширяет Consumer Super)?

Быстрый ответ: измените тип на List<? extends AbstractItem>

Почему нельзяВы просто назначаете это?

Представьте код здесь ...

List<AbstractItem> foo = new ArrayList<SharpItem>();
foo.add(new BluntItem());

Статическая типизация говорит, что это должно работать ... но вы не можете этого сделать!Это нарушит тип ArrayList.Вот почему это запрещено.Если вы измените его на

List<? extends AbstractItem> foo = new ArrayList<SharpItem>();

, вы сможете выполнить назначение, но никогда ничего не добавляете в список.Тем не менее, вы по-прежнему можете извлекать элементы из списка как AbstractItems.

Является ли просто использование List (чистого типа) хорошим решением?

Нет, определенно нет: -р

5 голосов
/ 21 сентября 2010

Вот пара дополнительных идей.Оставьте все то же самое, но используйте это:

interface AbstractToolbox {
    public List<? extends AbstractItem> getItems();
}

Это в основном говорит о том, что элементы абстрактного класса - неизвестный тип, но подклассы могут сделать его конкретным.Это потребует от вас вызова getItems() для ссылки типа ExорогоToolbox или CheapToolbox, чтобы иметь возможность получить список, который позволяет добавлять элементы и т. Д.

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

В качестве альтернативы, вы можете просто набрать AbstractToolbox:

public interface AbstractToolbox<T extends AbstractItem> {
    public List<T> getItems();
}
public ExpensiveToolbox implements AbstractToolbox<SharpItem> {
    public List<SharpItem> getItems() { //...
}
0 голосов
/ 21 сентября 2010
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) 
        ... ;

}
...