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

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

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

  • Что такое ограниченный шаблон и что является неограниченным шаблоном?

Ответ 1

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

Учебные руководства по Java имеют довольно хорошие объяснения обобщений в статьях Wildcards и More Fun with Wildcards.

Ответ 2

Если у вас есть иерархия классов 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 B, где B - некоторый тип. То есть тип неизвестен, но на нем может быть помечена "привязка". В этом случае он ограничен некоторым классом, который является подклассом B.

Ответ 3

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

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

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

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

- src является производителем E

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

- dst является потребителем E

Ответ 4

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

Ответ 5

В общем случае

Если структура содержит элементы с типом формы ? 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));
         }
    }

Ответ 6

Предварительно требования

public class A { }
public class B extends A { }
public class C extends A { }

List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>();

Эта проблема

listB = listA; //not compiled
listA = listB; //not compiled

listB = listA; В listA вы можете вставить объекты, которые являются экземплярами A или подклассами A (B и C). Когда вы делаете listB = listA вы можете рисковать тем, что listA содержит не-B объекты. Затем, когда вы попытаетесь вывести объекты из listB вы рискуете вывести объекты, не относящиеся к B (например, A или C). Это нарушает договор об listB переменной listB.

listA = listB; Если бы вы могли сделать это назначение, было бы возможно вставить экземпляры A и C в список, на который указывает listB. Вы можете сделать это через ссылку listA, которая объявлена как List. Таким образом, вы можете вставить не-B объекты в список, объявленный для хранения экземпляров B (или B подкласса).

enter image description here

Эта проблема

Как видите, проблема в назначениях. Например, вы не можете создать метод, который обрабатывает элементы List, которые являются родственниками, таким образом.

Решение

Общие Wildcards на помощь

Вы должны использовать List<? extends A> List<? extends A> ака верхняя граница ака ковариация ака производителей, если вы собираетесь читать .get() из списка

Когда вы знаете, что экземпляры в коллекции являются экземплярами A или подклассами A, можно безопасно прочитать экземпляры коллекции и привести их к экземплярам A.

Вы не можете вставлять элементы в список, потому что вы не знаете, относится ли список к классу A, B или C.

Вы должны использовать List<? super A> List<? super A> ака нижняя граница ака контравариантность ака потребители, если вы собираетесь вставить .add() в список

Когда вы знаете, что список типизирован либо для A, либо для суперкласса A, можно безопасно вставить в список экземпляры A или подклассы A (например, B или C).

Вы не можете читать из списка, за исключением случаев, когда он преобразует прочитанные объекты в объект. Элементы, уже присутствующие в списке, могут относиться к любому типу, который является либо A, либо суперклассом A, но невозможно точно знать, какой это класс.

enter image description here

Пожалуйста, прочитайте больше здесь

Ответ 7

Создаются общие шаблоны для создания методов, которые работают с коллекцией более многократно.

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

  • Если этот метод считывает только объекты из List<A>, нам следует дать этому методу List<A-sub>. (Поскольку A-sub IS a A)
  • Если этот метод только вставляет объекты в List<A>, нам следует дать этому методу List<A-super>. (Потому что A IS A-super)