Lista <Mapa <Ciąg, Ciąg >> vs Lista <? rozszerza Map <String, String >>

129

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?

Eng. Fouad
źródło
2
Uwielbiam Javę, ale to jedna z rzeczy, które nie są tak dobre ...
Mite Mitreski
4
Czuję, że jeśli odczytujemy to jako „wszystko, co rozszerza…”, staje się jasne.
Śmieci
Niesamowite, ponad 12 tysięcy wyświetleń w około 3 dni? !!
Eng. Fouad,
5
Trafił na pierwszą stronę Hacker News. Gratulacje.
r3st0r3
1
@ Eng. Znalazłem tutaj. Nie ma już na pierwszej stronie, ale był wczoraj. news.ycombinator.net/item?id=3751901 (wczoraj w połowie niedzieli tutaj w Indiach)
r3st0r3

Odpowiedzi:

181

Różnica polega na tym, że na przykład plik

List<HashMap<String,String>>

jest

List<? extends Map<String,String>>

ale nie

List<Map<String,String>>

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 Listz HashMaps powinno być a Listz Maps, 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 Listz HashMaps nie powinno być a Listz Maps.

prawdomówność
źródło
6
Wciąż HashMapjest to Mapspowodowane polimorfizmem.
inż Fouad
46
Racja, ale a Listz HashMaps nie jest Listz Maps.
trutheality
1
Nazywa się to kwantyfikacją ograniczoną
Dan Burton
Dobry przykład. Warto również zauważyć, że nawet jeśli zadeklarujesz List<Map<String,String>> maps = hashMaps; i HashMap<String,String> aMap = new HashMap<String, String>();, nadal stwierdzisz, że maps.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)
Raze
@Raze nie, faktycznie jesteś może dodać HashMapdo listy Maps, zarówno swoimi przykładami są legalne, jeśli czytam je poprawnie.
prawdomówność
24

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>do List<Object>zobaczycie zillion inne pytania na SO).

Tom Hawtin - haczyk
źródło
1
może to dalej wyjaśnić? nie rozumiem ani nie mam linku do dobrych praktyk?
Samir Mangroliya
3
@Samir Wyjaśnij co? List<String>nie jest podtypem List<Object>? - patrz na przykład stackoverflow.com/questions/3246137/ ...
Tom Hawtin - tackline
2
To nie wyjaśnia, jaka jest różnica między z lub bez ? extends. Nie wyjaśnia też korelacji z super / podtypami lub współ / kontrawariancją (jeśli istnieje).
Abel
16

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, to Cari Trucksą 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świadczenie List<Map<String, String>>nie może pochodzić z Map, ale musi być Map .

Dlatego zadziała, ponieważ TreeMapdziedziczy po Map:

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 extendssł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ć extendw 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>());
Abel
źródło
Nic w „Więc co to jest z…” nie zostanie skompilowane.
NobleUplift
@NobleUplift: trudno ci pomóc, jeśli nie dostarczysz komunikatu o błędzie, który otrzymujesz. Rozważ również, jako alternatywę, zadanie nowego pytania na temat SO, aby uzyskać odpowiedź, większą szansę na sukces. Powyższy kod to tylko fragmenty, zależy to od tego, czy zaimplementowałeś go w swoim scenariuszu.
Abel
Nie mam nowego pytania, mam ulepszenia. List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());powoduje 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>());działa świetnie. Ostatni przykład jest oczywiście poprawny.
NobleUplift
@NobleUplift: przepraszam, długo podróżuję, naprawię, gdy wrócę, i dziękuję za wskazanie rażącego błędu! :)
Abel
Nie ma problemu, po prostu cieszę się, że zostanie to naprawione. Wprowadziłem zmiany za Ciebie, jeśli chcesz je zatwierdzić.
NobleUplift
4

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ąć Setz Aobiektów, które pierwotnie napisałem z tym podpisem:

void myMethod(Set<A> set)

Ale tak naprawdę chce to nazwać Sets z podklas A. Ale to nie jest dozwolone! (Przyczyną tego jest to, że myMethodmożna dodać do settego obiekty typu A, 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 settyp staje się zbiorem obiektów rzeczywistego podtypu A, dzięki czemu można go używać z podklasami bez narażania systemu typów.

Rörd
źródło
0

Jak wspomniałeś, mogą istnieć dwie poniższe wersje definiowania listy:

  1. List<? extends Map<String, String>>
  2. 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 Listmogą przechowywać obiekty danego typu, wprowadzono generics Java ? extends. Więc w # 1, Listmoże zawierać dowolny obiekt, który pochodzi od Map<String, String>typu. Dodanie dowolnego innego typu danych spowodowałoby zgłoszenie wyjątku.

Funsuk Wangadu
źródło