Есть ли разница между
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
}
Вы могли бы подумать, что a List
of HashMap
s должно быть a 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)
Вот почему a List
of HashMap
s не должно быть a List
of Map
s.
HashMap
это Map
связано с полиморфизмом.
List
of HashMap
s не a List
of Map
s.
List<Map<String,String>> maps = hashMaps;
и HashMap<String,String> aMap = new HashMap<String, String>();
, вы все равно обнаружите, что maps.add(aMap);
это незаконно, хотя hashMaps.add(aMap);
это законно. Цель состоит в том, чтобы предотвратить добавление неправильных типов, но это не позволит добавить правильные типы (компилятор не может определить «правильный» тип во время компиляции)
HashMap
в список Map
s, оба ваших примера являются законными, если я их правильно читаю.
Вы не можете назначать выражения с такими типами, как List<NavigableMap<String,String>>
первый.
(Если вы хотите знать , почему вы не можете назначить List<String>
для List<Object>
увидим мильона других вопросов на SO.)
List<String>
это не подтип List<Object>
? - см., например, stackoverflow.com/questions/3246137/…
? extends
. Он также не объясняет корреляцию с супер / подтипами или ко / контравариантностью (если таковая имеется).
В других ответах мне не хватает ссылки на то, как это относится к ко- и контравариантности, а также к под- и супертипам (то есть полиморфизму) в целом и к Java в частности. ОП может это хорошо понять, но на всякий случай вот оно:
Если у вас есть класс Automobile
, то Car
и Truck
есть их подтипы. Любой автомобиль можно присвоить переменной типа «Автомобиль», это хорошо известно в объектно ориентированном стиле и называется полиморфизмом. Ковариация относится к использованию того же принципа в сценариях с обобщенными элементами или делегатами. У 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());
Вы даже можете вызвать его с помощью компаратора, который сравнивает объекты и использует его с любым типом.
Немного ОТ, возможно, вы не спрашивали, но это помогает понять ответ на ваш вопрос. В общем, когда вы что-то получаете , используйте ковариацию, а когда что- то помещаете , используйте контравариантность. Лучше всего это объяснить в ответе на вопрос о переполнении стека. Как контравариантность использоваться в дженериках 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
ключевого слова применяется только к этому параметру, а не ко всем остальным. Т.е. не будут компилироваться:
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>());
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
приводит к found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds
. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
работает отлично. Последний пример, очевидно, верен.
Сегодня я использовал эту функцию, поэтому вот мой очень свежий пример из реальной жизни. (Я изменил имена классов и методов на общие, чтобы они не отвлекали от сути дела.)
У меня есть метод , который означал , чтобы принять Set
из A
объектов , которые я изначально писал с этой подписью:
void myMethod(Set<A> set)
Но на самом деле он хочет назвать это Set
s подклассов 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 ? extends
. Итак, в №1 List
может содержаться любой объект, производный от Map<String, String>
типа. Добавление любого другого типа данных вызовет исключение.