Список> против списка> - PullRequest
124 голосов
/ 21 марта 2012

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

List<Map<String, String>>

и

List<? extends Map<String, String>>

?

Если нет никакой разницы, какая выгода от использования ? extends?

Ответы [ 5 ]

175 голосов
/ 21 марта 2012

Разница в том, что, например,

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 из HashMap с должно быть List из Map с, но есть веская причина, почему это не так:

Предположим, вы могли бы сделать:

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 из HashMap с не должно быть List из Map с.

25 голосов
/ 21 марта 2012

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

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

16 голосов
/ 28 марта 2012

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

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

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

Возможно, немного OT, выне спрашивал, но помогает понять, отвечая на ваш вопрос.В общем, когда вы получаете что-то, используйте ковариацию, а когда вы кладете что-то, используйте контравариантность.Это лучше всего объяснить в ответе на вопрос переполнения стека Как будет использоваться контравариантность в обобщениях Java? .

Так что же тогда с List<? extends Map<String, String>>

Вы используете extends, поэтому применяются правила для ковариации .Здесь у вас есть список карт, и каждый элемент, который вы сохраняете в списке, должен быть Map<string, string> или производным от него.Оператор List<Map<String, String>> не может быть производным от Map, но должен быть a 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>());
4 голосов
/ 22 марта 2012

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

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

0 голосов
/ 22 марта 2012

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

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

...