Чи є різниця між
List<Map<String, String>>
і
List<? extends Map<String, String>>
?
Якщо різниці немає, яка користь від використання ? extends
?
Чи є різниця між
List<Map<String, String>>
і
List<? extends Map<String, String>>
?
Якщо різниці немає, яка користь від використання ? extends
?
Відповіді:
Різниця полягає в тому, що, наприклад, 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
с.
HashMap
це Map
пов'язано з поліморфізмом.
List
з HashMap
ї НЕ List
з Map
с.
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>
побачити ще мільйон інших питань.
List<String>
не є підтипом List<Object>
? - див., наприклад, stackoverflow.com/questions/3246137/…
? extends
. Також не пояснюється кореляція із супер / підтипами чи ко / протиріччя (якщо вони є).
Що мені не вистачає в інших відповідях - це посилання на те, як це стосується ко- та протиріччя та суб- та супертипів (тобто поліморфізму) взагалі та 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>());
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
тип 's набуває сукупність об'єктів фактичного підтипу A
, тому стає можливим використовувати це для підкласів, не загрожуючи системі типів.
Як ви згадали, нижче можуть бути дві версії визначення списку:
List<? extends Map<String, String>>
List<?>
2 дуже відкритий. Він може містити будь-який тип об'єкта. Це може бути не корисно, якщо ви хочете мати карту заданого типу. У разі , якщо хто - то випадково поміщає інший тип карти, наприклад, Map<String, int>
. Ваш споживчий метод може зламатися.
Для того, щоб забезпечити це List
можуть вміщуватись об’єкти заданого типу, введено загальну мову Java ? extends
. Отже, у №1 List
може містити будь-який об’єкт, який походить від Map<String, String>
типу. Додавання будь-якого іншого типу даних призведе до виключення.