По сути, List<List<?>>
и List<? extends List<?>>
имеют разные аргументы типа.
На самом деле один является подтипом другого, но сначала давайте узнаем больше о том, что они означают по отдельности.
Понимание семантических различий
Вообще говоря, подстановочный знак ?
представляет некоторую «отсутствующую информацию». Это означает "здесь когда-то был аргумент типа, но мы больше не знаем, что это такое". И поскольку мы не знаем, что это такое, накладываются ограничения на то, как мы можем использовать все, что относится к этому конкретному аргументу типа.
На данный момент давайте упростим пример, используя List
вместо Map
.
List<List<?>>
содержит список любого типа с аргументом любого типа. То есть:
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, но мы больше не знаем, что это такое. То есть:
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>>
? Или List<Vector<Float>>
? Мы не знаем.)
Мы можем собрать их вместе и получить List<? extends List<?>>
. Мы больше не знаем, какой тип List
в нем содержится, и мы также не знаем тип элемента тех List
. То есть:
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
Возможно, вы знаете, что "concrete" универсальные типы обладают инвариантностью< /em>, то есть 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
:
Рассматриваемый
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
и всем, что в нем.
Смотрите также
person
Radiodef
schedule
02.04.2014
test(Map<?,List<?>>)
в типеMain
неприменим для аргументов(Map<Integer,List<String>>)
- person Louis Bliss   schedule 02.04.2014Map<?, List<?>>
. Затем вам нужно будет использовать метод сMap<Integer, List<?>> mappy = new HashMap<>();
в качестве аргумента. - person Balder   schedule 02.04.2014mappy.put(123, new ArrayList<Integer>())
без ошибок, что нежелательно. - person Louis Bliss   schedule 02.04.2014