Универсальный класс Java и шаблоны - PullRequest
4 голосов
/ 15 декабря 2009

У меня проблема с общими классами в Java.

У меня есть этот класс:

public abstract class MyMotherClass<C extends AbstractItem> 
{
    private C   item;

    public void setItem(C item)
    {
        this.item = item;
    }

    public C getItem()
    {
        return item;
    }
}

Реализация этого класса может быть:

public class MyChildClass extends MyMotherClass<ConcreteItem>
{

}

ConcreteItem - это простой класс, расширяющий AbstractItem (который является абстрактным).

поэтому у MyChildClass есть ConcreteItem, и я могу использовать:

MyChildClass child = new MyChildClass();
child.setItem(new ConcreteItem());

// automatic cast due to generic class
ConcreteItem item = child.getItem();

Хорошо, на данный момент все хорошо. Вот проблема:

Теперь я хочу извлечь из коллекции экземпляр MyMotherClass и установить его элемент (тип которого неизвестен):

Map<String, MyMotherClass> myCollection = new HashMap<String, MyMotherClass>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass child = myCollection.get("key");
child.setItem(myItems.get("key2"));

Если я так делаю, он запускается. НО у меня есть предупреждение, потому что MyMotherClass является универсальным типом, и я не использую универсальный тип. Но я не знаю, какой тип моего извлеченного ребенка, поэтому я хочу использовать подстановочный знак:

Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass<?> child = myCollection.get("key");
child.setItem(myItems.get("key2"));

И вот проблема: у меня ошибка компиляции, которая говорит: Метод setItem (capture # 1-of?) В типе MyMotherClass не применим для аргументов (AbstractItem)

и когда я пытаюсь использовать унаследованный шаблон, та же проблема:

Map<String, MyMotherClass<? extends AbstractItem>> myCollection = new HashMap<String, MyMotherClass<? extends AbstractItem>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections
...


MyMotherClass<? extends AbstractItem> child = myCollection.get("key");
child.setItem(myItems.get("key2"));

что я могу сделать?

спасибо и извините за мой английский, который не очень хорошо;)

Ответы [ 5 ]

6 голосов
/ 15 декабря 2009

Для операций write требуется super подстановочный знак.

final Map<String, MyMotherClass<? super AbstractItem>> myCollection =
    new HashMap<String, MyMotherClass<? super AbstractItem>>();

final Map<String, AbstractItem> myItems = 
    new HashMap<String, AbstractItem>();

...

final MyMotherClass<? super AbstractItem> child = 
    myCollection.get("key");
child.setItem(myItems.get("key2"));

extends подстановочный знак для read операций.

РЕДАКТИРОВАТЬ: В ответ на ваш комментарий.

Прежде всего, оцените, действительно ли вам нужны символы подстановки.

Если ответ все еще положительный, то вы можете изначально инициализировать коллекции более конкретным типом, а затем уменьшить их до ограниченных подстановочных знаков, например

final Map<String, MyChildClass> myInitCollection =
    new HashMap<String, MyChildClass>();

final Map<String, ConcreteItem> myInitItems = 
    new HashMap<String, ConcreteItem>();

myInitCollection.put( "key", new MyChildClass( )  );

final MyMotherClass< ConcreteItem> child = 
    myInitCollection.get("key");

child.setItem(myInitItems.get("key2"));

final Map<String, ? extends MyMotherClass< ? extends AbstractItem >>
    myCollection = myInitCollection;

final Map<String, ? extends AbstractItem> myItems = myInitItems; 

Однако обратите внимание, что myCollection все еще нельзя безопасно преобразовать в Map<String, MyMotherClass< ? extends AbstractItem>>.

Кроме того, прочитайте эту статью об ограниченных подстановочных параметрах и о том, когда их следует избегать.

2 голосов
/ 15 декабря 2009

Я мог бы что-то упустить, но почему бы не сделать следующее в вашем классе MyMotherClass, используя явный класс AbstractItem вместо универсального класса C?

public abstract class MyMotherClass<C extends AbstractItem> {

    private AbstractItem item;

    public void setItem(AbstractItem item) {
        this.item = item;
    }

    public AbstractItem getItem() {
        return this.item;
    }

}

Одно только это изменение позволит вам использовать ваш шаблон:

Map<String, MyMotherClass<?>> myCollection = new HashMap<String, MyMotherClass<?>>();
Map<String, AbstractItem> myItems = new HashMap<String, AbstractItem>();

// fill the 2 collections

MyMotherClass<?> child = myCollection.get("key");
child.setItem(myItems.get("key2"));

без ошибок.

Конечно, в MyChildClass вы можете переопределить MyMotherClass#getItem() следующим образом:

@Override
public ConcreteItem getItem() {
    return (ConcreteItem) super.getItem();
}

чтобы убедиться, что правильный класс возвращается; этот же подход для всех подклассов MyMotherClass позволит вам возвращать правильные типы.

1 голос
/ 15 декабря 2009

Проблема в том, что компилятор не может узнать, является ли AbstractItem, полученный вами от myItems, правильным типом для MyMotherClass, полученным из myCollection. Возможно, вы пытаетесь положить ConcreteItem2 в MyMotherClass<ConcreteItem1>

Один из способов справиться с этим - определить myCollection как Map<String, MyMotherClass<AbstractItem>>. Тогда вы сможете поместить любой AbstractItem в любой из объектов, которые вы получите от myCollection. Однако вы потеряете способность получить ConcreteItem из getItem().

Не думаю, что предложение Александра Погребняка действительно работает в этом случае. Его идея в основном эквивалентна простому использованию MyMotherClass<AbstractItem>, поскольку в вашем случае параметр типа должен расширяться AbstractItem, и он объявляет его ? super AbstractItem, единственный класс, который удовлетворяет обоим, - это AbstractItem.

В зависимости от того, как у вас настроены вещи, вы можете сделать что-то, используя объект Class. Например, если ваша карта myCollection также включает в себя объект Class для каждого MyMotherClass, представляющего тип элемента, который он содержит, вы можете использовать его для приведения элемента или даже дополнительно добавить карту myItems по типу. в строку.

0 голосов
/ 16 декабря 2009

На ваш вопрос нет ответа, поэтому вы должны принять мой ответ.

Если вы даже не знаете фактический тип элементов, как может компилятор знать? Вы пытаетесь вставить предмет, тип которого вы не знаете, в контейнер, тип предмета которого вы не знаете, вы идете по тонкому льду.

Иногда программисты знают больше о типах данных времени выполнения, чем компилятор, в этом случае для успокоения компилятора необходимы приведение и предупреждение подавления.

В вашем случае вы даже не знаете типы данных, и вы хотите, чтобы компилятор мог волшебным образом проверить типы для вас. Это не может быть сделано.

0 голосов
/ 15 декабря 2009

Дополнение: ссылка на официальную ссылку

Sun говорит (из Использование и программирование обобщений в J2SE 5.0 )

Существует три типа подстановочных знаков:

  1. «? Extends Type»: обозначает семейство подтипов типа Type. Это самый полезный шаблон
  2. «? Супер Тип»: обозначает семейство супертипов типа Тип
  3. "?": Обозначает набор всех типов или любой

Как @ alexander-pogrebnyak говорит в своем ответе, вы должны использовать super здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...