Может кто-нибудь объяснить, что делает <? super T> и когда он должен использоваться и как эта конструкция должна сотрудничать с <T> и <? расширяет T>?
Я использую generics довольно долгое время, но никогда не использовал конструкцию типа List<? super T>
.
Что это значит? Как это использовать? Как это выглядит после стирания?
Я также задаюсь вопросом: это что-то стандартное в родовом программировании (программирование шаблона?) или это просто "изобретение" Java? Может ли С# разрешить подобные конструкции?
Ответы
Ответ 1
Эта конструкция используется, когда вы хотите потреблять предметы из коллекции в другую коллекцию. Например. у вас есть общий Stack
, и вы хотите добавить метод popAll
, который принимает параметр Collection as и выталкивает из него все элементы из стека. По здравому смыслу этот код должен быть законным:
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 означает продюсер-extends, consumer-super.
Подробнее см. Эффективный Java 2nd Ed., Item 28.
Ответ 2
Это называется "ограниченным подстановочным знаком". Он очень хорошо объяснил в официальном учебнике.
Как указано в учебнике, вы знаете, что список содержит объекты только одного подтипа T
Например, List<? extends Number>
может содержать только Integer
или только Long
s, но не оба.
Ответ 3
Эти вещи известны в теории типов как дисперсия, причем <? extends T>
является ко-вариантной нотацией, а <? super T>
является контравариантной нотацией. Самое простое объяснение состоит в том, что ?
может быть заменено любым типом, продолжающим T
в ко-вариантной нотации, а ?
может быть заменен любым типом, который T
распространяется в противоположном варианте.
Использование co и contra-variance намного сложнее, чем может показаться на первый взгляд, особенно потому, что дисперсия "переключается" в зависимости от положения.
Простым примером может служить класс функций. Скажем, у вас есть функция, которая принимает A
и возвращает a B
. Правильная нотация для него заключалась бы в том, что A
является контравариантным и B
os ко-вариантом. Чтобы лучше понять, как это имеет место, рассмотрим метод - позвоним ему g
- который получает этот гипотетический класс функций, где f должен получать Arc2D
и возвращать a Shape
.
Внутри g
этот f
называется передачей Arc2D
, а возвращаемое значение используется для инициализации Area
(который ожидает a Shape
).
Теперь предположим, что передаваемый f
принимает любой Shape
и возвращает Rectangle2D
. Так как a Arc2D
также a Shape
, то g
не получит ошибку, передающую Arc2D
в f
, а так как a Rectangle2D
также является Shape
, то это может быть передан в конструктор Area
.
Если вы попытаетесь инвертировать любую из дисперсий или заменить ожидаемые и фактические типы в этом примере, вы увидите, что это не сработало. У меня нет времени прямо сейчас, чтобы записать этот код, и моя Java во всяком случае очень ржавая, но я увижу, что я могу сделать позже - если никто не будет достаточно любезен, чтобы сделать это сначала.
Ответ 4
Часто задаваемые вопросы по Java Generics имеют хорошее объяснение по поводу дженериков Java. Проверьте вопрос Что такое ограниченный подстановочный знак?, в котором подробно объясняется использование конструкции "? T".