#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:
Просто подумал, что я бы добавил к этой старой теме, обобщив свойства параметров списка, созданных с помощью типов или подстановочных знаков….
Когда метод имеет параметр/результат, представляющий собой список, использование экземпляра типа или подстановочных знаков определяет
- Типы списков, которые могут быть переданы методу в качестве аргумента
- Типы списков, которые могут быть заполнены из результата метода
- Типы элементов, которые могут быть записаны в список в рамках метода
- Типы, которые могут быть заполнены при чтении элементов из списка в методе
Параметр/Тип возврата: List< Foo>
- Типы списков, которые могут быть переданы методу в качестве аргумента:
List< Foo>
- Типы списков, которые могут быть заполнены из результата метода:
List< Foo>
List< ? super Foo>
List< ? super SubFoo>
List< ? extends Foo>
List< ? extends SuperFoo>
- Типы элементов, которые могут быть записаны в список в рамках метода:
Foo
amp; подтипы
- Типы, которые могут быть заполнены при чтении элементов из списка в методе:
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>
- Типы элементов, которые могут быть записаны в список в рамках метода:
- Ни одного! Добавить невозможно.
- Типы, которые могут быть заполнены при чтении элементов из списка в методе:
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
amp; супертипы
- Типы, которые могут быть заполнены при чтении элементов из списка в методе:
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;
Дело в том, что это было разработано не для локальных переменных/членов, а для сигнатур функций, вот почему это так задом наперед.