Как назначить список<? расширяет BaseClass> до List<BaseClass>

Имеющий

class BaseClass implements IData ();
class ChildClassA() extends BaseClass;
class ChildClassB() extends BaseClass;

так как не может сделать

List<BaseClass> aList = new ArrayList<ChildClassA>()

так что есть

List<? extends IData> aList 
for pointint to either 
    ArrayList<ChildClassA>(),  
or 
    ArrayList<ChildClassB>()

aList создается другой маршрутизацией во время выполнения, и эта часть кода имеет функцию для List<IData> от aList

вопрос в том, если List<? extends IData> aList это точка для ArrayList<ChildClassA>() или же ArrayList<ChildClassB>(),

это может сделать ListData<IData> outputList = (List<IData>) aList? что-то вроде ниже:

(кажется, это работает, но не уверен, есть ли лучший способ назначить массив обобщений, кроме приведения.)

Редактировать: вывод List<IData> outputList предназначен только для чтения (неизменяемый), без вставки / удаления для него, он будет только повторять IData, чтобы реагировать на то, чем в действительности является IData.

List<? extends IData> aList =  new ArrayList<ChildClassA>();
ListData<IData> outputList = (List<IData>)aList

List<? extends IData> aList =  new ArrayList<ChildClassB>();
ListData<IData> outputList = (List<IData>)aList

2 ответа

Решение

TL; Dr Использование Collections#unmodifiableList:

List<IData> outputList = Collections.unmodifiableList(aList);

Для получения дополнительной информации по этой теме вы можете ознакомиться с принципом PECS.


Это невозможно, потому что эти два типа несовместимы.

List<BaseClass> это только то, что объявлено, список BaseClass объекты. Точнее, это дает две гарантии:

  • объекты, извлеченные из него, могут быть назначены BaseClass
  • каждый объект, который может быть назначен BaseClass может быть добавлено к нему (и никакому другому)

List<? extends BaseClass> это более свободная декларация. Точно, это просто не дает второй гарантии. Однако не только гарантия исчезла, но и теперь невозможно добавить элементы в нее, поскольку точный общий тип списка не определен. Это может даже измениться для того же объявления списка (не того же самого объекта списка) во время выполнения.

Как следствие, List<? extends BaseClass> не присваивается List<BaseClass>, поскольку последний дает гарантию, первый не может выполнить.


На практике рассмотрим следующий метод:

public List<BaseClass> makeList() {
    // TODO implement method
    return null;
}

Если кто-то реализует этот метод, возвращая List<? extends BaseClass> клиент, использующий этот метод, не сможет добавить элементы к нему, хотя в его объявлении указано иное.

Из-за этого такое назначение приводит к ошибке компиляции.

Чтобы исправить проблему, приведенную в примере, к методу может быть добавлено свободное объявление:

public List<? extends BaseClass> makeList() {
    // TODO implement method
    return null;
}

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


Теперь вернемся к вашему варианту использования. На мой взгляд, наиболее подходящим решением будет перефразировать функцию, которая

возьмите [s] список из списка a List.

Как кажется, в настоящее время он объявлен как

public void useList(List<BaseClass> list);

но поскольку он не добавляет элементы в список, он должен быть объявлен как

public void useList(List<? extends BaseClass> list);

Однако, если этот метод является частью неизменяемого в настоящее время API, вы все равно можете сделать:

List<? extends BaseClass> list;
....
List<BaseClass> tmp = Collections.unmodifiableList(list);
useList(tmp);

Нет, это небезопасно.

После этого произнесения было бы законно добавить в список, который должен содержать только ChildClassA типизированные элементы, элементы другого дочернего типа ChildClassB типа и наоборот.

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

List<ChildClassA> aList =  new ArrayList<ChildClassA>();
aList.add(a1);
aList.add(a2);
//...
List<IData> iDataList = (List<IData>) aList;
iDataList.add(b1);
iDataList.add(b2);
//...
for (ChildClassA a : aList) {
   // a some point a is going to be assigned b1 or b2 and they results in cast
   // exception.
}

Обратите внимание, что iDataList делает ссылку на тот же объект списка, что и aList, Если это приведение разрешено, вы сможете добавить элементы aList это не ChildClassA экземпляров.

Лучшее решение на деталях.

Если проблема в том, что сторонняя библиотека требует List<IData> типизированная ссылка и до тех пор, пока она предназначена только для чтения, вы можете использовать неизменяемый прокси, возвращаемый Collections.unmodifiableList:

import java.util.Collections;
//...
final List<ChildClassA> aList = new ArrayList<>();
//... add stuff to aList
final List<IData> readOnlyIDataList = Collections.unmodifiableList(aList); 
//... read only access operations readOnlyIDataList 
Другие вопросы по тегам