Есть ли разница между
List<Map<String, String>>
и
List<? extends Map<String, String>>
?
Если нет никакой разницы, в чем преимущество использования ? extends
?
Есть ли разница между
List<Map<String, String>>
и
List<? extends Map<String, String>>
?
Если нет никакой разницы, в чем преимущество использования ? extends
?
Отличие состоит в том, что, например, <
List<HashMap<String,String>>
является
List<? extends Map<String,String>>
но не
List<Map<String,String>>
Итак:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
Вы думаете, что List
of HashMap
должен быть List
of Map
s, но есть веская причина, почему это не так:
Предположим, вы могли:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
Итак, поэтому List
of HashMap
не должен быть List
Map
s.
Вы не можете назначать выражения с типами, такими как List<NavigableMap<String,String>>
, первым.
(Если вы хотите узнать, почему вы не можете назначить List<String>
на List<Object>
другие zillion вопросы о SO.)
То, что мне не хватает в других ответах, - это ссылка на то, как это относится к ко-и контравариантности, суб- и супертипам (то есть полиморфизму) в целом и к Java в частности. Это может быть хорошо понято OP, но на всякий случай, вот оно:
Если у вас есть класс Automobile
, то Car
и Truck
являются их подтипами. Любой автомобиль может быть назначен переменной типа Automobile, это хорошо известно в OO и называется полиморфизмом. Ковариация относится к использованию этого же принципа в сценариях с дженериками или делегатами. Java не имеет делегатов (пока), поэтому этот термин применим только к дженерикам.
Я склонен думать о ковариации как о стандартном полиморфизме, о том, что вы ожидаете работать, не думая, потому что:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
Причина ошибки, однако, правильная: List<Car>
не наследует от List<Automobile>
и поэтому не может быть назначено друг другу. Только общие параметры типа имеют отношение наследования. Можно подумать, что компилятор Java просто недостаточно умен, чтобы правильно понять ваш сценарий. Однако вы можете помочь компилятору, указав ему подсказку:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
Обратная ковариация - это контравариантность. Где в ковариации типы параметров должны иметь отношение подтипа, при контравариантности они должны иметь отношение супертипа. Это можно рассматривать как верхнюю границу наследования: любой супертип разрешен и включает указанный тип:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
Это можно использовать с Collections.sort:
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
Вы даже можете вызвать его с помощью компаратора, который сравнивает объекты и использует их с любым типом.
Немного OT, возможно, вы не спрашивали, но это помогает понять ответ на ваш вопрос. В общем, когда вы что-то получаете, используйте ковариацию, и когда вы что-то помещаете, используйте контравариантность. Это лучше всего объяснить в ответе на вопрос о переполнении стека. Как будет использоваться контравариантность в Java-дженериках?.
List<? extends Map<String, String>>
Вы используете extends
, поэтому применяются правила ковариации. Здесь у вас есть список карт, и каждый элемент, который вы храните в списке, должен быть Map<string, string>
или получен из него. Утверждение List<Map<String, String>>
не может быть получено из Map
, но должно быть Map
.
Следовательно, следующее будет работать, потому что TreeMap
наследует от Map
:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
но это не будет:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
и это тоже не сработает, потому что оно не удовлетворяет ограничению ковариации:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
Это, вероятно, очевидно, но вы, возможно, уже заметили, что использование ключевого слова extends
применяется только к этому параметру, а не к остальным. I.e., следующее не будет компилироваться:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Предположим, что вы хотите разрешить любой тип на карте, с ключом как строкой, вы можете использовать extend
для каждого параметра типа. Предположим, вы обрабатываете XML и хотите сохранить AttrNode, Element и т.д. На карте, вы можете сделать что-то вроде:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
Сегодня я использовал эту функцию, так что вот мой очень свежий пример в реальной жизни. (Я изменил имена классов и методов на общие, чтобы они не отвлекали от фактической точки.)
У меня есть метод, который должен был принять Set
из A
объектов, которые я изначально написал с помощью этой подписи:
void myMethod(Set<A> set)
Но он хочет называть его с помощью Set
подклассов A
. Но это не допускается! (Причина этого заключается в том, что myMethod
может добавлять объекты к Set
, которые имеют тип A
, но не подтип, который объявляется Set
объектами на сайте вызывающего абонента. Таким образом, это может нарушить тип если бы это было возможно.)
Теперь приступим к генерации для спасения, потому что он работает по назначению, если я использую эту подпись метода вместо:
<T extends A> void myMethod(Set<T> set)
или короче, если вам не нужно использовать фактический тип в теле метода:
void myMethod(Set<? extends A> set)
Таким образом, тип Set
становится совокупностью объектов фактического подтипа A
, поэтому становится возможным использовать его с подклассами без угрозы для системы типов.
Как вы упомянули, могут быть две версии версий списка:
List<? extends Map<String, String>>
List<?>
2 очень открыта. Он может содержать любой тип объекта. Это может быть не полезно, если вы хотите иметь карту определенного типа. Если кто-то случайно ставит другой тип карты, например, Map<String, int>
. Ваш потребительский метод может сломаться.
Чтобы гарантировать, что List
может содержать объекты определенного типа, Java generics представили ? extends
. Таким образом, в # 1 List
может содержать любой объект, полученный из типа Map<String, String>
. Добавление любого другого типа данных приведет к исключению.