У меня есть пара вопросов о общих подстановочных знаках в Java:
-
В чем разница между
List<? extends T>
иList<? super T>
? -
Что такое ограниченный шаблон и что является неограниченным шаблоном?
У меня есть пара вопросов о общих подстановочных знаках в Java:
В чем разница между List<? extends T>
и List<? super T>
?
Что такое ограниченный шаблон и что является неограниченным шаблоном?
В вашем первом вопросе, <? 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.
Если у вас есть иерархия классов 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.
Джош Блох также имеет хорошее объяснение, когда следует использовать 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
Бывают случаи, когда вы захотите ограничить типы типов, которым разрешено передавать параметр типа. Например, метод, который работает с числами, может только принимать экземпляры 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).
В общем случае
Если структура содержит элементы с типом формы
? 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));
}
}
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 подкласса).
Как видите, проблема в назначениях. Например, вы не можете создать метод, который обрабатывает элементы 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, но невозможно точно знать, какой это класс.
Пожалуйста, прочитайте больше здесь
Создаются общие шаблоны для создания методов, которые работают с коллекцией более многократно.
Например, если метод имеет параметр List<A>
, мы можем дать этому методу только List<A>
. Это отходы для этого метода funtion при некоторых обстоятельствах:
List<A>
, нам следует дать этому методу List<A-sub>
. (Поскольку A-sub IS a A)List<A>
, нам следует дать этому методу List<A-super>
. (Потому что A IS A-super)