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

#java #generics

Вопрос:

Почему я получаю ошибки компилятора с этим 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: «Не удается создать экземпляр списка массивов расширяет Foo>»
  • В строке 4: «Метод add(захват№1-из ? (Foo) в списке типов расширяет Foo> неприменимо для аргументов (SubFoo)»

Обновление: Благодаря Джеффу Си я могу изменить строку 3 на «новый список массивов<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> , компилятор не будет знать, что безопасно добавлять подпапку. Что, если бы ан ArrayList<AltFoo> был назначен на foos ? Это было бы допустимым назначением, но добавление подпапки приведет к загрязнению коллекции.

Ответ №2:

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

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

  1. Типы списков, которые могут быть переданы методу в качестве аргумента
  2. Типы списков, которые могут быть заполнены из результата метода
  3. Типы элементов, которые могут быть записаны в список в рамках метода
  4. Типы, которые могут быть заполнены при чтении элементов из списка в методе

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

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

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

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

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

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

Толкование/Комментарий

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

Комментарии:

1. Я думаю, что 3 и 4 для List<? super Foo> неправильны. Это должно быть «Foo amp; подтипы» и «Объект» соответственно.

Ответ №3:

Попробуй:

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

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

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

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

Комментарии:

1. Я не согласен с вашим последним замечанием. Тип возвращаемого значения с подстановочным знаком очень полезен, если вызывающему просто нужно что-то получить из списка.

Ответ №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;
 

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