Список <Карта <Рядок, Рядок >> проти списку <? розширює Map <String, String >>


129

Чи є різниця між

List<Map<String, String>>

і

List<? extends Map<String, String>>

?

Якщо різниці немає, яка користь від використання ? extends?


2
Я люблю Java, але це одна з речей, яка не є такою хорошою ...
Mite Mitreski

4
Я відчуваю, що якщо ми читатимемо це як "все, що розширюється ...", це стає зрозумілим.
Сміття

Неймовірно, понад 12К переглядів за 3 дні? !!
Eng.Fouad

5
Він дійшов до головної сторінки новин Hacker News. Поздоровлення
r3st0r3

1
@ Eng.Found it ось це. Більше не на першій сторінці, але був там вчора. news.ycombinator.net/item?id=3751901 (вчора середня неділя тут, в Індії)
r3st0r3

Відповіді:


180

Різниця полягає в тому, що, наприклад, a

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с.


6
Тим не менш, HashMapце Mapпов'язано з поліморфізмом.
Фуад

46
Правильно, але Listз HashMapї НЕ Listз Mapс.
trutheality


Хороший приклад. Також варто зазначити, що навіть якщо ви заявите 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>побачити ще мільйон інших питань.


1
Ви можете далі пояснити це? я не можу зрозуміти або будь-яке посилання на гарну практику?
Самір Мангролія

3
@Samir Поясніть, що? List<String>не є підтипом List<Object>? - див., наприклад, stackoverflow.com/questions/3246137/…
Том Хоутін - Tackline

2
Це не пояснює, в чому різниця між або без ? extends. Також не пояснюється кореляція із супер / підтипами чи ко / протиріччя (якщо вони є).
Авель

16

Що мені не вистачає в інших відповідях - це посилання на те, як це стосується ко- та протиріччя та суб- та супертипів (тобто поліморфізму) взагалі та Java, зокрема. Це може бути добре зрозуміло в ОП, але про всяк випадок, ось що:

Коваріація

Якщо у вас є клас 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());

Ви навіть можете назвати його компаратором, який порівнює об'єкти та використовує його з будь-яким типом.

Коли використовувати контра або ко-дисперсію?

Трохи ОТ, можливо, ви не питали, але це допомагає зрозуміти відповіді на ваше запитання. Взагалі, коли ви щось отримуєте , використовуйте коваріацію, а коли щось кладете , використовуйте протиріччя. Це найкраще пояснюється у відповіді на запитання про переповнення стека. Як застосовуватиметься протиріччя у Java generics? .

Так що ж це тоді з 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>());

Нічого в "Отже, що це тоді з ..." не складеться.
NobleUplift

@NobleUplift: важко допомогти вам, якщо ви не надасте повідомлення про помилку, яке ви отримаєте. Розглянемо також як альтернативу, щоб задати нове запитання щодо ТА, щоб отримати відповідь, більший шанс на успіх. Код, наведений вище, - це просто фрагменти, це залежить від того, що ви його реалізували у своєму сценарії.
Авель

У мене немає нового питання, у мене є поліпшення. 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)

Але ми хочемо насправді назвати це з Sets підкласів A. Але це не дозволено! (Причиною цього є те, що myMethodможна додати об’єкти до setтипу A, але не до підтипу, який setоб'єкти оголошуються на сайті абонента. Тому це може зламати систему типів, якщо це можливо.)

Тепер тут на допомогу приходять дженерики, оскільки він працює за призначенням, якщо замість цього я використовую підпис цього методу:

<T extends A> void myMethod(Set<T> set)

або коротше, якщо вам не потрібно використовувати фактичний тип у тілі методу:

void myMethod(Set<? extends A> set)

Таким чином, setтип 's набуває сукупність об'єктів фактичного підтипу A, тому стає можливим використовувати це для підкласів, не загрожуючи системі типів.


0

Як ви згадали, нижче можуть бути дві версії визначення списку:

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

2 дуже відкритий. Він може містити будь-який тип об'єкта. Це може бути не корисно, якщо ви хочете мати карту заданого типу. У разі , якщо хто - то випадково поміщає інший тип карти, наприклад, Map<String, int>. Ваш споживчий метод може зламатися.

Для того, щоб забезпечити це List можуть вміщуватись об’єкти заданого типу, введено загальну мову Java ? extends. Отже, у №1 Listможе містити будь-який об’єкт, який походить від Map<String, String>типу. Додавання будь-якого іншого типу даних призведе до виключення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.