Список <Карта <String, String >> vs List <? расширяет карту <String, String >>

Есть ли разница между

List<Map<String, String>>

и

List<? extends Map<String, String>>

?

Если нет никакой разницы, в чем преимущество использования ? extends?

Ответ 1

Отличие состоит в том, что, например, <

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.

Ответ 2

Вы не можете назначать выражения с типами, такими как List<NavigableMap<String,String>>, первым.

(Если вы хотите узнать, почему вы не можете назначить List<String> на List<Object> другие zillion вопросы о SO.)

Ответ 3

То, что мне не хватает в других ответах, - это ссылка на то, как это относится к ко-и контравариантности, суб- и супертипам (то есть полиморфизму) в целом и к 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());

Вы даже можете вызвать его с помощью компаратора, который сравнивает объекты и использует их с любым типом.

Когда использовать contra или со-дисперсию?

Немного 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>());

Ответ 4

Сегодня я использовал эту функцию, так что вот мой очень свежий пример в реальной жизни. (Я изменил имена классов и методов на общие, чтобы они не отвлекали от фактической точки.)

У меня есть метод, который должен был принять 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, поэтому становится возможным использовать его с подклассами без угрозы для системы типов.

Ответ 5

Как вы упомянули, могут быть две версии версий списка:

  • List<? extends Map<String, String>>
  • List<?>

2 очень открыта. Он может содержать любой тип объекта. Это может быть не полезно, если вы хотите иметь карту определенного типа. Если кто-то случайно ставит другой тип карты, например, Map<String, int>. Ваш потребительский метод может сломаться.

Чтобы гарантировать, что List может содержать объекты определенного типа, Java generics представили ? extends. Таким образом, в # 1 List может содержать любой объект, полученный из типа Map<String, String>. Добавление любого другого типа данных приведет к исключению.