Ответ 1
По существу, List<List<?>>
и List<? extends List<?>>
имеют разные аргументы типа.
На самом деле это тот факт, что один является подтипом другого, но сначала дайте больше узнать о том, что они означают индивидуально.
Понимание семантических различий
Вообще говоря, подстановочный знак ?
представляет собой некоторую "недостающую информацию". Это означает, что здесь был один аргумент типа, но мы не знаем, что это такое ". И поскольку мы не знаем, что это такое, накладываются ограничения на то, как мы можем использовать что-либо, относящееся к этому конкретному аргументу типа.
На данный момент упростите пример, используя List
вместо Map
.
-
A
List<List<?>>
содержит любой вид List с любым аргументом типа. Итак, i.e.:List<List<?>> theAnyList = new ArrayList<List<?>>(); // we can do this theAnyList.add( new ArrayList<String>() ); theAnyList.add( new LinkedList<Integer>() ); List<?> typeInfoLost = theAnyList.get(0); // but we are prevented from doing this typeInfoLost.add( new Integer(1) );
Мы можем поместить любой
List
вtheAnyList
, но тем самым мы потеряли знание своих элементов. -
Когда мы используем
? extends
,List
содержит определенный подтип List, но мы не знаем, что это такое. Итак, i.e.:List<? extends List<Float>> theNotSureList = new ArrayList<ArrayList<Float>>(); // we can still use its elements // because we know they store Float List<Float> aFloatList = theNotSureList.get(0); aFloatList.add( new Float(1.0f) ); // but we are prevented from doing this theNotSureList.add( new LinkedList<Float>() );
Невозможно добавить что-либо в
theNotSureList
, потому что мы не знаем фактического типа его элементов. (Первоначально он былList<LinkedList<Float>>
? Или aList<Vector<Float>>
? Мы не знаем.) -
Мы можем объединить их и иметь
List<? extends List<?>>
. Мы не знаем, какой типList
он имеет в нем больше, и мы не знаем тип элемента этихList
. Итак, i.e.:List<? extends List<?>> theReallyNotSureList; // these are fine theReallyNotSureList = theAnyList; theReallyNotSureList = theNotSureList; // but we are prevented from doing this theReallyNotSureList.add( new Vector<Float>() ); // as well as this theReallyNotSureList.get(0).add( "a String" );
Мы потеряли информацию как о
theReallyNotSureList
, так и о типе элементаList
внутри него.(Но вы можете заметить, что мы можем назначить для него любые списки, содержащие списки...)
Итак, чтобы разбить его:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
Map
работает одинаково, у него просто больше параметров типа:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Для чего нужен ? extends
Вы можете знать, что "конкретные" общие типы имеют инвариантность, то есть List<Dog>
не является подтипом List<Animal>
, даже если class Dog extends Animal
. Вместо этого подстановочный знак - это то, как мы имеем ковариацию, т.е. List<Dog>
является подтипом List<? extends Animal>
.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
Итак, применяя эти идеи к вложенному List
:
-
List<String>
является подтипомList<?>
, ноList<List<String>>
не является подтипомList<List<?>>
. Как показано выше, это не позволяет нам скомпрометировать безопасность типов, добавляя неправильные элементы вList
. -
List<List<String>>
является подтипомList<? extends List<?>>
, потому что ограниченный подстановочный знак допускает ковариацию. То есть? extends
позволяет считать, чтоList<String>
является подтипомList<?>
. -
List<? extends List<?>>
на самом деле является общим супертипом:List<? extends List<?>> ╱ ╲ List<List<?>> List<List<String>>
В обзоре
-
Map<Integer, List<String>>
принимает толькоList<String>
как значение. -
Map<?, List<?>>
принимает любое значениеList
как значение. -
Map<Integer, List<String>>
иMap<?, List<?>>
- это разные типы, которые имеют отдельную семантику. - Нельзя преобразовать в другое, чтобы предотвратить внесение изменений небезопасным способом.
-
Map<?, ? extends List<?>>
- это общий супертип, который накладывает безопасные ограничения:Map<?, ? extends List<?>> ╱ ╲ Map<?, List<?>> Map<Integer, List<String>>
Как работает общий метод работы
Используя параметр типа в методе, мы можем утверждать, что List
имеет некоторый конкретный тип.
static <E> void test(Map<?, List<E>> m) {}
Это конкретное объявление требует, чтобы все List
в Map
имели один и тот же тип элемента. Мы не знаем, что это такое на самом деле, но мы можем использовать его абстрактным образом. Это позволяет нам выполнять "слепые" операции.
Например, такой вид объявления может быть полезен для некоторого накопления:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
Мы не можем называть put
на m
, потому что мы не знаем, что его тип ключа больше. Однако мы можем манипулировать его значениями, потому что мы понимаем, что они все List
с тем же типом элемента.
Только для пинков
Другой вариант, который не обсуждается, состоит в том, чтобы иметь как ограниченный подстановочный знак, так и общий тип для List
:
static <E> void test(Map<?, ? extends List<E>> m) {}
Мы могли бы назвать это чем-то вроде Map<Integer, ArrayList<String>>
. Это наиболее разрешительное объявление, если мы только заботимся о типе E
.
Мы также можем использовать границы для определения типов типа:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
Это и разрешительно, что мы можем передать ему, так и разрешительно о том, как мы можем манипулировать m
и все в нем.
См. также
- "Java Generics: Что такое PECS?" для разницы между
? extends
и? super
. - JLS 4.10.2. Подтипирование между классами и типами интерфейсов и JLS 4.5.1. Тип Аргументы параметризованных типов для ввода указывает на технические детали этого ответа.