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

129

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

List<Map<String, String>>

и

List<? extends Map<String, String>>

?

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

Eng.Fouad
источник
2
Я люблю java, но это одна из вещей, которые не так хороши ...
Mite Mitreski
4
Я чувствую, что если мы читаем это как «все, что расширяется ...», становится ясно.
Garbage
Невероятно, более 12 тысяч просмотров примерно за 3 дня? !!
Eng. Fouad
5
Он попал на первую страницу Hacker News. Congrats.
r3st0r3
1
@ Eng. Нашел вот оно. Больше не на первой полосе, но был там вчера. news.ycombinator.net/item?id=3751901 (вчера была середина воскресенья здесь, в Индии.)
r3st0r3,

Ответы:

180

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

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

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

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 Listof HashMaps не должно быть a Listof Maps.

trutheality
источник
6
Тем не менее, HashMapэто Mapсвязано с полиморфизмом.
Eng. Fouad
46
Верно, но a Listof HashMaps не a Listof Maps.
Trutheality
1
Это называется ограниченным количественным определением
Дэн Бертон
Хороший пример. Также стоит отметить, что даже если вы заявите List<Map<String,String>> maps = hashMaps; и HashMap<String,String> aMap = new HashMap<String, String>();, вы все равно обнаружите, что maps.add(aMap);это незаконно, хотя hashMaps.add(aMap);это законно. Цель состоит в том, чтобы предотвратить добавление неправильных типов, но это не позволит добавить правильные типы (компилятор не может определить «правильный» тип во время компиляции)
Raze
@Raze нет, на самом деле вы можете добавить a HashMapв список Maps, оба ваших примера являются законными, если я их правильно читаю.
Trutheality
24

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

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

Том Хотин - tackline
источник
1
может дальше это объяснить? я не могу понять или какая-либо ссылка для хорошей практики?
Samir Mangroliya
3
@Samir Объясни, что? List<String>это не подтип List<Object>? - см., например, stackoverflow.com/questions/3246137/…
Tom Hawtin - tackline
2
Это не объясняет, в чем разница между с или без ? extends. Он также не объясняет корреляцию с супер / подтипами или ко / контравариантностью (если таковая имеется).
Abel
16

В других ответах мне не хватает ссылки на то, как это относится к ко- и контравариантности, а также к под- и супертипам (то есть полиморфизму) в целом и к 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>());
Абель
источник
Ничего из "Так что это тогда с ..." не компилируется.
NobleUplift
@NobleUplift: вам будет сложно помочь, если вы не предоставите сообщение об ошибке, которое получите. В качестве альтернативы рассмотрите возможность задать новый вопрос по SO, чтобы получить ответ, больший шанс на успех. Приведенный выше код - это просто фрагменты, это зависит от того, реализовали вы его в своем сценарии.
Авель
У меня нет нового вопроса, есть улучшения. 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>());работает отлично. Последний пример, очевидно, верен.
NobleUplift
@NobleUplift: извините, долго путешествую, исправлю, когда вернусь, и спасибо, что указали на вопиющую ошибку! :)
Абель
Нет проблем, я просто рад, что это исправят. Я внес для вас правки, если вы хотите их утвердить.
NobleUplift
4

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

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

Rörd
источник
0

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

  1. List<? extends Map<String, String>>
  2. List<?>

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

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

Funsuk Wangadu
источник