Czy jest jakaś różnica między
List<Map<String, String>>
i
List<? extends Map<String, String>>
?
Jeśli nie ma różnicy, jakie są korzyści z używania ? extends
?
java
generics
inheritance
polymorphism
Eng. Fouad
źródło
źródło
Odpowiedzi:
Różnica polega na tym, że na przykład plik
jest
ale nie
Więc:
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 }
Można by pomyśleć, że a
List
zHashMap
s powinno być aList
zMap
s, ale jest dobry powód, dla którego tak nie jest:Załóżmy, że możesz zrobić:
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)
Dlatego właśnie a
List
zHashMap
s nie powinno być aList
zMap
s.źródło
HashMap
jest toMap
spowodowane polimorfizmem.List
zHashMap
s nie jestList
zMap
s.List<Map<String,String>> maps = hashMaps;
iHashMap<String,String> aMap = new HashMap<String, String>();
, nadal stwierdzisz, żemaps.add(aMap);
jest to nielegalne, chociażhashMaps.add(aMap);
jest legalne. Celem jest zapobieżenie dodaniu niewłaściwych typów, ale nie pozwoli to na dodanie właściwych typów (kompilator nie może określić "właściwego" typu w czasie kompilacji)HashMap
do listyMap
s, zarówno swoimi przykładami są legalne, jeśli czytam je poprawnie.Nie można przypisywać wyrażeń z typami takimi jak
List<NavigableMap<String,String>>
do pierwszego.(Jeśli chcesz wiedzieć, dlaczego nie można przypisać
List<String>
doList<Object>
zobaczycie zillion inne pytania na SO).źródło
List<String>
nie jest podtypemList<Object>
? - patrz na przykład stackoverflow.com/questions/3246137/ ...? extends
. Nie wyjaśnia też korelacji z super / podtypami lub współ / kontrawariancją (jeśli istnieje).To, czego brakuje mi w innych odpowiedziach, to odniesienie do tego, jak odnosi się to do współ- i kontrawariancji oraz pod- i nadtypów (to znaczy polimorfizmu) w ogóle, a zwłaszcza do Javy. OP może to dobrze zrozumieć, ale na wszelki wypadek, oto jest:
Kowariancja
Jeśli masz klasę
Automobile
, toCar
iTruck
są jej podtypy. Każdy samochód może być przypisany do zmiennej typu Automobile, jest to dobrze znane w OO i nazywa się polimorfizmem. Kowariancja odnosi się do stosowania tej samej zasady w scenariuszach z typami generycznymi lub delegatami. Java nie ma (jeszcze) delegatów, więc termin dotyczy tylko typów ogólnych.Zwykle myślę o kowariancji jako standardowym polimorfizmie, którego można by oczekiwać bez zastanowienia, ponieważ:
List<Car> cars; List<Automobile> automobiles = cars; // You'd expect this to work because Car is-a Automobile, but // throws inconvertible types compile error.
Przyczyna błędu jest jednak poprawna:
List<Car>
nie dziedziczy,List<Automobile>
a zatem nie może być do siebie przypisana. Tylko parametry typu ogólnego mają relację dziedziczenia. Można by pomyśleć, że kompilator Javy po prostu nie jest wystarczająco inteligentny, aby właściwie zrozumieć twój scenariusz. Możesz jednak pomóc kompilatorowi, dając mu wskazówkę:List<Car> cars; List<? extends Automobile> automobiles = cars; // no error
Sprzeczność
Odwrotnością ko-wariancji jest kontrawariancja. W przypadku gdy w kowariancji typy parametrów muszą mieć relację podtypów, w przypadku kontrawariancji muszą mieć relację nadtypu. Można to uznać za górną granicę dziedziczenia: dozwolony jest dowolny nadtyp, w tym określony typ:
class AutoColorComparer implements Comparator<Automobile> public int compare(Automobile a, Automobile b) { // Return comparison of colors }
Może być używany z Kolekcje.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());
Możesz nawet wywołać to za pomocą modułu porównującego, który porównuje obiekty i używa go z dowolnym typem.
Kiedy stosować kontrwariancję lub współwariancję?
Być może trochę OT, nie pytałeś, ale pomaga to zrozumieć odpowiedź na twoje pytanie. Ogólnie rzecz biorąc, kiedy coś otrzymujesz , użyj kowariancji, a kiedy coś umieścisz , użyj kontrawariancji. Najlepiej można to wyjaśnić w odpowiedzi na pytanie dotyczące przepełnienia stosu. W jaki sposób kontrawariancja byłaby używana w generycznych programach Java? .
Więc z czym to jest
List<? extends Map<String, String>>
Używasz
extends
, więc obowiązują zasady dotyczące kowariancji . Tutaj masz listę map, a każdy element, który przechowujesz na liście, musi byćMap<string, string>
lub pochodzić z niej. OświadczenieList<Map<String, String>>
nie może pochodzić zMap
, ale musi byćMap
.Dlatego zadziała, ponieważ
TreeMap
dziedziczy poMap
:List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
ale to nie będzie:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
i to też nie zadziała, ponieważ nie spełnia ograniczenia kowariancji:
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
Co jeszcze?
Jest to prawdopodobnie oczywiste, ale być może już zauważyłeś, że użycie
extends
słowa kluczowego dotyczy tylko tego parametru, a nie reszty. Oznacza to, że nie można skompilować:List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>(); mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Załóżmy, że chcesz zezwolić na dowolny typ w mapie, z kluczem jako ciągiem, którego możesz użyć
extend
w każdym parametrze typu. To znaczy, przypuśćmy, że przetwarzasz XML i chcesz przechowywać AttrNode, element itp. Na mapie, możesz zrobić coś takiego:List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...; // Now you can do: listOfMapsOfNodes.add(new TreeMap<Sting, Element>()); listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
źródło
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
powodujefound: ? 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>());
działa świetnie. Ostatni przykład jest oczywiście poprawny.Dzisiaj korzystałem z tej funkcji, więc oto mój bardzo świeży przykład z życia. (Zmieniłem nazwy klas i metod na ogólne, aby nie odwracały uwagi od rzeczywistego punktu).
Mam metodę, która oznaczała przyjąć
Set
zA
obiektów, które pierwotnie napisałem z tym podpisem:void myMethod(Set<A> set)
Ale tak naprawdę chce to nazwać
Set
s z podklasA
. Ale to nie jest dozwolone! (Przyczyną tego jest to, żemyMethod
można dodać doset
tego obiekty typuA
, ale nie podtypu thatset
obiekty są zadeklarowane jako w miejscu wywołującego. Mogłoby to więc zepsuć system typów, gdyby było to możliwe).Teraz na ratunek przychodzą generics, ponieważ działa zgodnie z przeznaczeniem, jeśli zamiast tego używam tej sygnatury metody:
<T extends A> void myMethod(Set<T> set)
lub krócej, jeśli nie musisz używać rzeczywistego typu w treści metody:
void myMethod(Set<? extends A> set)
W ten sposób
set
typ staje się zbiorem obiektów rzeczywistego podtypuA
, dzięki czemu można go używać z podklasami bez narażania systemu typów.źródło
Jak wspomniałeś, mogą istnieć dwie poniższe wersje definiowania listy:
List<? extends Map<String, String>>
List<?>
2 jest bardzo otwarta. Może pomieścić dowolny typ obiektu. Może to nie być przydatne w przypadku, gdy chcesz mieć mapę określonego typu. Gdyby ktoś przypadkowo umieścił mapę innego typu, np.
Map<String, int>
. Twoja metoda konsumencka może się zepsuć.Aby upewnić się, że
List
mogą przechowywać obiekty danego typu, wprowadzono generics Java? extends
. Więc w # 1,List
może zawierać dowolny obiekt, który pochodzi odMap<String, String>
typu. Dodanie dowolnego innego typu danych spowodowałoby zgłoszenie wyjątku.źródło