Я использую довольно долгое время дженериков, но я никогда не использовал конструкцию как List<? super T>
.
Что это значит? Как использовать его? Как это заботится о стирании?
Я также задаюсь вопросом: это - что-то стандартное в универсальном программировании (шаблонное программирование?) или это - просто Java 'изобретение'? c#, например, позволяют подобные конструкции?
Эта конструкция используется, когда вы хотите использовать элементы из коллекции в другую коллекцию. Например, у вас есть общий Stack
, и вы хотите добавить метод popAll
, который принимает в качестве параметра коллекцию Collection и забирает в нее все элементы из стека. По здравому смыслу, этот код должен быть законным:
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ... ;
numberStack.popAll(objects);
но он компилируется, только если вы определите popAll
следующим образом:
// Wildcard type for parameter that serves as an E consumer
public void popAll(Collection<? super E> dst) {
while (!isEmpty())
dst.add(pop());
}
Другая сторона медали в том, что pushAll
должен быть определен следующим образом:
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
Обновление: Джош Блох распространил эту мнемонику, чтобы помочь вам запомнить, какой тип подстановочного знака использовать:
PECS означает производитель-расширяет, потребитель-супер.
Подробнее см. в Effective Java 2nd Ed., Item 28.
Это называется «ограниченный подстановочный знак». Это очень хорошо объяснено в официальном руководстве .
Как указано в руководстве, вы знаете, что список содержит объекты ровно одного подтипа T
Например, List extends Number>
может содержать только Integer
s или только Long
s, но не то и другое одновременно.
В FAQ по Java Generics есть хорошее объяснение о Java generics. Проверьте вопрос Что такое ограниченный подстановочный знак?, в котором подробно объясняется использование конструкции "? super T".
Эти вещи известны в теории типов как variance, причем extends T>
является ко-вариантной нотацией, а super T>
- контравариантной нотацией. Самое простое объяснение состоит в том, что ?
можно заменить любым типом, расширяющим T
в ко-инвариантной нотации, а ?
можно заменить любым типом, который T
расширяет в контравариантной.
Использование ко- и контравариантности гораздо сложнее, чем может показаться на первый взгляд, особенно потому, что дисперсия "переключается" в зависимости от позиции.
Простым примером может служить класс функций. Скажем, у вас есть функция, которая принимает A
и возвращает B
. Правильной нотацией для этого будет сказать, что A
является контравариантной, а B
- ковариантной. Чтобы лучше понять, как это происходит, рассмотрим метод - назовем его g
- который получает этот гипотетический класс function, где f должен получать Arc2D
и возвращать Shape
.
Внутри g
эта f
вызывается, передавая Arc2D
, а возвращаемое значение используется для инициализации Area
(которая ожидает Shape
).
Теперь предположим, что переданная вами f
принимает любую Shape
и возвращает Rectangle2D
. Поскольку Arc2D
также является Shape
, то g
не получит ошибку при передаче Arc2D
в f
, а поскольку Rectangle2D
также является Shape
, то его можно передать конструктору Area
.
Если вы попытаетесь инвертировать любую из вариаций или поменять местами ожидаемый и фактический типы в этом примере, вы увидите, что это не удастся. У меня сейчас нет времени, чтобы записать этот код, и моя Java довольно ржавая, но я посмотрю, что я смогу сделать позже - если никто не будет достаточно добр, чтобы сделать это первым.