Подтвердить что ты не робот

Может кто-нибудь объяснить, что делает <? super T> и когда он должен использоваться и как эта конструкция должна сотрудничать с <T> и <? расширяет T>?

Я использую generics довольно долгое время, но никогда не использовал конструкцию типа List<? super T>.

Что это значит? Как это использовать? Как это выглядит после стирания?

Я также задаюсь вопросом: это что-то стандартное в родовом программировании (программирование шаблона?) или это просто "изобретение" Java? Может ли С# разрешить подобные конструкции?

4b9b3361

Ответ 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 во всяком случае очень ржавая, но я увижу, что я могу сделать позже - если никто не будет достаточно любезен, чтобы сделать это сначала.