Дженерики Java (подстановочные знаки)

110

У меня есть пара вопросов об общих подстановочных знаках в Java:

  1. В чем разница между List<? extends T>и List<? super T>?

  2. Что такое ограниченный шаблон и что такое неограниченный шаблон?

Пабло Фернандес
источник
В случае, если ответ Теда Гао был удален (поскольку он был только для ссылки), вот сообщение в блоге, на которое он ссылается.
royhowie

Ответы:

123

В вашем первом вопросе <? extends T>и <? super T>приведены примеры ограниченных подстановочных знаков. Неограниченный подстановочный знак выглядит <?>и в основном означает <? extends Object>. Это примерно означает, что общий тип может быть любого типа. Ограниченный подстановочный знак ( <? extends T>или <? super T>) накладывает ограничение на тип, говоря, что он либо должен расширять определенный тип ( <? extends T>известный как верхняя граница), либо должен быть предком определенного типа ( <? super T>известен как нижняя граница) .

Учебные пособия по Java содержат довольно хорошее объяснение универсальных шаблонов в статьях « Подстановочные знаки» и « Еще больше удовольствия с подстановочными знаками» .

Билл Ящерица
источник
Просто чтобы понять, если A <B и B <C, тогда: <A extends C> неверно?
Пабло Фернандес,
Если под A <B вы имеете в виду, что A расширяет B, тогда A расширяет C. Однако вы бы не использовали это в синтаксисе с подстановочными знаками, вы бы сказали <? расширяет C>, чтобы ограничить ваш выбор A или B.
Bill the Lizard
и в том случае, если я скажу <? super C> какая бы разница?
Пабло Фернандес
3
Просто хотел порекомендовать еще одну ссылку на Java Generics: angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html
Зак Скривена 01
3
@Pablo: <? super C>будет означать, что ваш тип ограничен чем-то выше Cв иерархии типов. (Извините за очень поздний ответ. Думаю, у нас не было уведомлений о комментариях 2 года назад?)
Bill the Lizard
49

Если у вас есть иерархия классов A, B является подклассом A, а C и D оба являются подклассом B, как показано ниже

class A {}
class B extends A {}
class C extends B {}
class D extends B {}

затем

List<? extends A> la;
la = new ArrayList<B>();
la = new ArrayList<C>();
la = new ArrayList<D>();

List<? super B> lb;
lb = new ArrayList<A>(); //fine
lb = new ArrayList<C>(); //will not compile

public void someMethod(List<? extends B> lb) {
    B b = lb.get(0); // is fine
    lb.add(new C()); //will not compile as we do not know the type of the list, only that it is bounded above by B
}

public void otherMethod(List<? super B> lb) {
    B b = lb.get(0); // will not compile as we do not know whether the list is of type B, it may be a List<A> and only contain instances of A
    lb.add(new B()); // is fine, as we know that it will be a super type of A 
}

Ограниченный подстановочный знак похож на то, что ? extends BB - некоторый тип. То есть тип неизвестен, но на него можно поставить "привязку". В этом случае он ограничен некоторым классом, который является подклассом B.

Oxbow_lakes
источник
Я предполагаю, что List<? super B>описывает, как List принимает тип, который является родительским классом класса B ? Вот почему C и D не компилируются?
Volkan Güven
38

У Джоша Блоха также есть хорошее объяснение того, когда использовать, superи extendsв этом видео-разговоре google io он упоминает мнемонику Producer extendsConsumersuper .

Из слайдов презентации:

Предположим, вы хотите добавить массовые методы в Stack<E>

void pushAll(Collection<? extends E> src);

- src - производитель E

void popAll(Collection<? super E> dst);

- dst - потребитель E

пустой
источник
Я читал книгу Блоха, но до сих пор не вижу разницы между extends и super в данном конкретном случае.
Пабло Фернандес
Посмотрите видео, я думаю, это довольно понятно. Кроме того, я думаю, вам следует задать еще один вопрос о том, «в чем разница между List <? Extends T> и List <? Super T>», на который вы, надеюсь, получите больше ответов. (если да, добавьте ссылку отсюда)
пусто
2
Кровати - почему бы не включить пример в этот ответ, чтобы сделать его законченным и автономным. Ссылки эфемерны.
Джеймс Шек,
3

Могут быть моменты, когда вы захотите ограничить типы типов, которые разрешено передавать параметру типа. Например, метод, который работает с числами, может хотеть принимать только экземпляры Number или его подклассов. Для этого нужны параметры ограниченного типа.

Collection<? extends MyObject> 

означает, что он может принимать все объекты, которые имеют отношения IS- A с MyObject (т.е. любой объект, который является типом myObject, или мы можем сказать любой объект любого подкласса MyObject) или объект класса MyObject.

Например:

class MyObject {}

class YourObject extends MyObject{}

class OurObject extends MyObject{}

Затем,

Collection<? extends MyObject> myObject; 

будет принимать только MyObject или дочерние объекты MyObject (т.е. любой объект типа OurObject, YourObject или MyObject, но не любой объект суперкласса MyObject).

Сандип Кумар
источник
1

В основном,

Если структура содержит элементы с типом формы ? extends E, мы можем получить элементы из структуры, но мы не можем поместить элементы в структуру.

List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); // compile-time error
assert ints.toString().equals("[1, 2, 3.14]"); 

Чтобы поместить элементы в структуру нужен другой вид шаблона с именем Wildcards with super,

 List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
    List<Integer> ints = Arrays.asList(5, 6);
    Collections.copy(objs, ints);
    assert objs.toString().equals("[5, 6, four]");

    public static <T> void copy(List<? super T> dst, List<? extends T> src) {
          for (int i = 0; i < src.size(); i++) {
                dst.set(i, src.get(i));
         }
    }
Пратик Джоши
источник
1

Общие подстановочные знаки созданы, чтобы сделать методы, работающие с Collection, более пригодными для повторного использования.

Например, если у метода есть параметр List<A>, мы можем передать его только List<A>этому методу. При некоторых обстоятельствах это бесполезно для работы этого метода :

  1. Если этот метод только читает объекты List<A>, тогда нам должно быть разрешено передавать List<A-sub>этому методу. (Потому что A-sub ЯВЛЯЕТСЯ A)
  2. Если этот метод только вставляет объекты в List<A>, тогда нам должно быть разрешено передавать List<A-super>этому методу. (Потому что А-супер)
Taoxiaopang
источник