Чи є різниця між
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до списку Maps, обидва ваші приклади є законними, якщо я читаю їх правильно.
Ви не можете присвоювати вирази таким типам, як 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)
Але ми хочемо насправді назвати це з Sets підкласів 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>типу. Додавання будь-якого іншого типу даних призведе до виключення.