Как добавить элементы в общую коллекцию подстановочных знаков?

Почему я получаю ошибки компилятора с этим кодом Java?

1  public List<? extends Foo> getFoos()
2  {
3    List<? extends Foo> foos = new ArrayList<? extends Foo>();
4    foos.add(new SubFoo());
5    return foos;
6  }

Где "SubFoo" - это конкретный класс, который реализует Foo, а Foo - интерфейс.

Ошибки, которые я получаю с помощью этого кода:

  • В строке 3: "Невозможно создать экземпляр ArrayList <? extends Foo > "
  • В строке 4: "Метод add (capture # 1-of? extends Foo) в типе List < capture # 1-of? extends Foo > не применим для аргументов (SubFoo)"

Обновление: Благодаря Jeff C я могу изменить строку 3, чтобы сказать "новый ArrayList <Foo> ();". Но у меня все еще проблема с строкой 4.

Ответ 1

Используйте это вместо:

1  public List<? extends Foo> getFoos()
2  {
3    List<Foo> foos = new ArrayList<Foo>(); /* Or List<SubFoo> */
4    foos.add(new SubFoo());
5    return foos;
6  }

Как только вы объявляете foos как List<? extends Foo>, компилятор не знает, что безопасно добавлять SubFoo. Что, если ArrayList<AltFoo> было присвоено foos? Это будет действительное назначение, но добавление SubFoo будет загрязнять коллекцию.

Ответ 2

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

Если метод имеет параметр/результат, который является списком, использование экземпляра типа или подстановочных знаков определяет

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

Параметр/Тип возврата: List< Foo>

  • Типы списка, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
  • Типы списка, которые могут быть заполнены из результата метода:
    • List< Foo>
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
  • Типы элементов, которые могут быть записаны для перечисления в методе:
    • Foo и подтипы
  • Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Параметр/Тип возврата: List< ? extends Foo>

  • Типы списка, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
    • List< Subfoo>
    • List< SubSubFoo>
    • List< ? extends Foo>
    • List< ? extends SubFoo>
    • List< ? extends SubSubFoo>
  • Типы списка, которые могут быть заполнены из результата метода:
    • List< ? extends Foo>
    • List< ? extends SuperFoo>
    • List< ? extends SuperSuperFoo>
  • Типы элементов, которые могут быть записаны для перечисления в методе:
    • None! Невозможно добавить.
  • Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Параметр/Тип возврата: List<? super Foo>

  • Типы списка, которые могут быть переданы методу в качестве аргумента:
    • List< Foo>
    • List< Superfoo>
    • List< SuperSuperFoo>
    • List< ? super Foo>
    • List< ? super SuperFoo>
    • List< ? super SuperSuperFoo>
  • Типы списка, которые могут быть заполнены из результата метода:
    • List< ? super Foo>
    • List< ? super SubFoo>
    • List< ? super SubSubFoo>
  • Типы элементов, которые могут быть записаны для перечисления в методе:
    • Foo и супертипы
  • Типы, которые могут быть заполнены при чтении элементов из списка в методе:
    • Foo и супертипы (до Object)

Интерпретация/Комментировать

  • потребности внешних абонентов приводят дизайн декларации метода, то есть открытый API (обычно первичное рассмотрение)
  • потребности внутренней логики логических устройств приводят любые дополнительные решения к фактическим типам данных, объявленным и построенным внутренне (обычно вторичное рассмотрение)
  • используйте List<Foo>, если код вызывающего абонента всегда сфокусирован на управлении классом Foo, поскольку он максимизирует гибкость как для чтения, так и для записи.
  • используйте List<? extends UpperMostFoo>, если может быть много разных типов вызывающих, сфокусированных на управлении другим классом (не всегда Foo), и в иерархии типов Foo есть один самый верхний класс, и если метод должен внутренне писать в список и манипуляция списком вызывающего абонента читаются. Здесь метод может внутренне использовать List< UpperMostFoo> и добавлять к нему элементы, прежде чем возвращать List< ? extends UpperMostFoo>
  • если может быть много разных типов вызывающих абонентов, ориентированных на управление другим классом (не всегда Foo), и если требуется чтение и запись в список, а в иерархии типов Foo есть один самый низкий класс, то это имеет смысл использовать List< ? super LowerMostFoo>

Ответ 3

Try:

public List<Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}

В общем конструкторе ArrayList должен быть задан определенный тип, вы не можете использовать '?' wildcard там. Изменение экземпляра на "новый ArrayList <Foo> () 'разрешило бы первую ошибку компиляции.

Объявление переменной foos может иметь подстановочные знаки, но поскольку вы знаете точный тип, имеет смысл ссылаться на информацию о том же типе. Теперь вы говорите, что foos содержит определенный подтип Foo, но мы не знаем, что. Добавление SubFoo не может быть разрешено, поскольку SubFoo не является "всеми подтипами Foo". Изменение объявления на "List <Foo> foos = 'решает вторую ошибку компиляции.

Наконец, я бы изменил возвращаемый тип на "List <Foo> "; поскольку клиенты этого метода не смогут многое сделать с возвращаемым значением, определенным в настоящее время. Вы редко должны использовать подстановочные знаки в возвращаемых типах. При необходимости используйте параметризованную сигнатуру метода, но предпочитайте ограниченные типы появляться только в аргументах метода, так как это оставляет ее вызывающему, который может передавать определенные типы и работать и соответственно.

Ответ 4

Следующее будет работать нормально:

public List<? extends Foo> getFoos() {
    List<Foo> foos = new ArrayList<Foo>();
    foos.add(new SubFoo());
    return foos;
}

Ответ 5

Чтобы узнать, как работают дженерики, посмотрите этот пример:

    List<SubFoo> sfoo = new ArrayList<SubFoo>();
    List<Foo> foo;
    List<? extends Foo> tmp;

    tmp = sfoo;
    foo = (List<Foo>) tmp;

Дело в том, что это не было предназначено для локальных/членных переменных, но для сигнатур функций, почему это так задне-назад.