Разница между <? super T> и <? extends T> в Java
Вопрос о различиях между List<? super T>
и List<? extends T>
в Java
Я столкнулся с проблемой при работе с обобщениями в Java и не могу понять разницу между двумя типами списков: List<? super T>
и List<? extends T>
.
Я использовал List<? extends T>
, но заметил, что не могу добавлять элементы в этот список с помощью метода list.add(e)
. В то же время, с использованием List<? super T>
у меня не возникает этой проблемы, и я могу успешно добавлять элементы.
Можете ли вы объяснить различия между этими двумя конструкциями и в каких случаях следует использовать каждую из них? Спасибо!
5 ответ(ов)
Я согласен с ответом @Bert F, но вот как я это понимаю.
У меня есть объект X в руках. Если я хочу записать свой X в список, этот список должен быть либо списком X, либо списком объектов, к которым мой X может быть приведен при добавлении, т.е. любым суперклассом X...
List<? super X>
Если я получаю список и хочу прочитать X из этого списка, лучше, чтобы это был список X или список объектов, которые могут быть приведены к X при чтении, т.е. любым объектом, который расширяет X...
List<? extends X>
Вам нужно визуализировать разницу между List<? extends T>
и List<? super T>
. Рассмотрим пример с классами:
class A { }
class B extends A { }
class C extends B { }
List<? extends T>
— чтение и присваивание:
|-------------------------|-------------------|---------------------------------|
| подстановочный знак | чтение | присваивание |
|-------------------------|-------------------|---------------------------------|
| List<? extends C> | A B C | List<C> |
|-------------------------|-------------------|---------------------------------|
| List<? extends B> | A B | List<B> List<C> |
|-------------------------|-------------------|---------------------------------|
| List<? extends A> | A | List<A> List<B> List<C> |
|-------------------------|-------------------|---------------------------------|
List<? super T>
— запись и присваивание:
|-------------------------|-------------------|-------------------------------------------|
| подстановочный знак | добавление | присваивание |
|-------------------------|-------------------|-------------------------------------------|
| List<? super C> | C | List<Object> List<A> List<B> List<C> |
|-------------------------|-------------------|-------------------------------------------|
| List<? super B> | B C | List<Object> List<A> List<B> |
|-------------------------|-------------------|-------------------------------------------|
| List<? super A> | A B C | List<Object> List<A> |
|-------------------------|-------------------|-------------------------------------------|
Важные моменты:
- Всегда можно получить элемент типа
Object
из списка, независимо от подстановочного знака. - Всегда можно добавить
null
в изменяемый список, независимо от подстановочного знака.
Вопрос о том, как использовать ограниченные подстановочные знаки в Java может быть довольно запутанным, особенно когда речь идет о различиях между "super" и "extends".
В соответствии с материалом по Java Generics (например, из учебника Oracle), "super" обозначает нижнюю границу для обобщенных типов, а "extends" — верхнюю границу.
Синтаксис "? super T" указывает на неизвестный тип, который является суперклассом T (или самим T; не забывайте, что отношение суперкласса рефлексивно). Это позволяет вам принимать типы, которые находятся выше T в иерархии наследования.
В противоположность этому, "extends" используется для обозначения верхней границы. Когда вы пишете "? extends T", это означает, что вы работаете с типом, который является подклассом T.
Таким образом, использование "? super T" удобно в тех случаях, когда вам нужно записывать в структуру данных, поскольку вы можете добавлять подтипы T и любой из их суперклассов. А "? extends T" лучше подходит для чтения данных, так как это гарантирует, что вы сможете работать только с подтипами T.
Надеюсь, это проясняет различия между ними!
Когда вы работаете с списками в Java, важно понимать разницу между List<? extends X>
и List<? super X>
в контексте добавления и получения элементов.
Добавление элемента в список:
List<? extends X> не позволяет добавлять ничего, кроме
null
, в список. Это связано с тем, что компилятор не знает, какого конкретного типа объекты содержит список, так как он может содержать какие угодно подтипы X.List<? super X> позволяет добавлять объекты, которые являются X или его подтипами, включая
null
. Здесь компилятор знает, что вы можете добавлять элементы определенного типа, поскольку он может гарантировать, что список принимает экземпляры этого типа.
Получение элемента из списка:
Когда вы получаете элемент из List<? extends X>, вы можете присвоить его переменной типа X или любому супертипу X, включая Object. Это возможно, так как вы точно знаете, что элемент будет неким типом, который является подтипом X.
Однако когда вы получаете элемент из List<? super X>, вы можете присвоить его только переменной типа
Object
, поскольку компилятор не знает, какого конкретного типа элемент находится в списке.
Примеры:
List<? extends Number> list1 = new ArrayList<Integer>();
list1.add(null); // OK
Number n = list1.get(0); // OK
Serializable s = list1.get(0); // OK
Object o = list1.get(0); // OK
list1.add(2.3); // ERROR
list1.add(5); // ERROR
list1.add(new Object()); // ERROR
Integer i = list1.get(0); // ERROR
List<? super Number> list2 = new ArrayList<Number>();
list2.add(null); // OK
list2.add(2.3); // OK
list2.add(5); // OK
Object o = list2.get(0); // OK
list2.add(new Object()); // ERROR
Number n = list2.get(0); // ERROR
Serializable s = list2.get(0); // ERROR
Integer i = list2.get(0); // ERROR
Таким образом, обращение к типам с использованием wildcards (? extends
и ? super
) влияет на то, что вы можете добавлять в список и как вы можете извлекать из него значения.
Вы можете ознакомиться со всеми ответами выше, чтобы понять, почему использование .add()
ограничено '<?>'
, '<? extends>'
и частично '<? super>'
.
Но вот краткое резюме, если вы хотите запомнить это и не хотите каждый раз искать ответ:
List<? extends A>
означает, что этот список будет принимать любой List
типа A
и его подклассов. Однако вы не можете добавлять в этот список ничего, даже объекты типа A
.
List<? super A>
означает, что этот список будет принимать любой список типа A
и суперклассов A
. Вы можете добавлять в этот список объекты типа A
и его подклассы.
Инициализация ArrayList в одну строчку
Как инициализировать статическую Map?
Является ли List<Собака> подклассом List<Животное>? Почему дженерики в Java не являются неявно полиморфными?
Что такое PECS (Producer Extends Consumer Super)?
Как инициализировать значения HashSet при создании?