W tym przykładzie:
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile()
nie można skompilować z:
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
podczas gdy compiles()
jest akceptowany przez kompilator.
Ta odpowiedź wyjaśnia, że jedyną różnicą jest to, że w przeciwieństwie do <? ...>
, <T ...>
pozwala odwoływać się do typu później, co nie wydaje się być prawdą.
Jaka jest różnica między <? extends Number>
iw <T extends Number>
tym przypadku i dlaczego pierwsza kompilacja nie jest wykonywana?
Odpowiedzi:
Definiując metodę z następującym podpisem:
i wywoływanie go w następujący sposób:
W jls §8.1.2 stwierdzamy, że (interesująca część pogrubiona przeze mnie):
Innymi słowy, typ
T
jest dopasowywany do typu wejściowego i przypisywanyInteger
. Podpis stanie się skuteczniestatic void compiles(Map<Integer, List<Integer>> map)
.Jeśli chodzi o
doesntCompile
metodę, jls określa zasady subtypowania ( pogrubione przeze mnie p. 4.5.1 ):Oznacza to, że
? extends Number
rzeczywiście zawieraInteger
lub nawetList<? extends Number>
zawieraList<Integer>
, ale nie jest tak w przypadkuMap<Integer, List<? extends Number>>
iMap<Integer, List<Integer>>
. Więcej na ten temat można znaleźć w tym wątku SO . Nadal możesz sprawić, by wersja ze?
znakiem wieloznacznym działała, deklarując, że spodziewasz się podtypuList<? extends Number>
:źródło
? extends Number
raczej niż? extends Numeric
. [2] Twoje twierdzenie, że „nie jest tak w przypadku List <? Extends Number> i List <Integer>” jest błędne. Jak już wskazał @VinceEmigh, możesz stworzyć metodęstatic void demo(List<? extends Number> lst) { }
i wywołać ją w tendemo(new ArrayList<Integer>());
lub inny sposóbdemo(new ArrayList<Float>());
, a kod kompiluje się i działa OK. A może źle interpretuję lub nie rozumiem tego, co powiedziałeś?List<? extends Number>
jako parametr typu całej mapy, a nie samej. Dziękuję bardzo za komentarz.List<Number>
nie zawieraList<Integer>
. Załóżmy, że masz funkcjęstatic void check(List<Number> numbers) {}
. Gdy wywoływaniecheck(new ArrayList<Integer>());
go nie kompiluje, musisz zdefiniować metodę jakostatic void check(List<? extends Number> numbers) {}
. Z mapą jest tak samo, ale z większą liczbą zagnieżdżeń.Number
jest parametrem typu listy i należy go dodać,? extends
aby był kowariantem,List<? extends Number>
jest parametrem typuMap
i również wymaga? extends
kowariancji.W rozmowie:
T jest dopasowane do liczby całkowitej, więc typ argumentu to
Map<Integer,List<Integer>>
. W przypadku tej metody tak nie jestdoesntCompile
: typ argumentu pozostajeMap<Integer, List<? extends Number>>
bez względu na faktyczny argument w wywołaniu; i nie można tego przypisaćHashMap<Integer, List<Integer>>
.AKTUALIZACJA
W
doesntCompile
metodzie nic nie stoi na przeszkodzie, aby zrobić coś takiego:Oczywiście nie może przyjąć
HashMap<Integer, List<Integer>>
argumentu.źródło
doesntCompile
? Po prostu ciekawi mnie to.doesntCompile(new HashMap<Integer, List<? extends Number>>());
działałby tak samodoesntCompile(new HashMap<>());
.HashMap<Integer, List<Integer>>
”. Czy mógłbyś wyjaśnić, dlaczego nie można tego przypisać?Uproszczony przykład demonstracji. Ten sam przykład można wizualizować jak poniżej.
List<Pair<? extends Number>>
jest wielopoziomowym typem symboli wieloznacznych, podczas gdyList<? extends Number>
jest standardowym typem symboli wieloznacznych.Prawidłowe konkretne instancje typu dzikiej karty
List<? extends Number>
obejmująNumber
i wszelkie podtypy,Number
podczas gdy w przypadkuList<Pair<? extends Number>>
których jest argumentem typu argument typu i sam ma konkretną instancję typu ogólnego.Generyczne są niezmienne, więc
Pair<? extends Number>
typ wieloznaczny może tylko zaakceptowaćPair<? extends Number>>
. Typ wewnętrzny? extends Number
jest już kowariantny. Musisz ustawić typ zamykający jako kowariant, aby umożliwić kowariancję.źródło
<Pair<Integer>>
działa,<Pair<? extends Number>>
ale nie działa<T extends Number> <Pair<T>>
?T
vs?
. Część problemu polega na tym, że kiedy Andronik osiąga zasadniczy punkt swojego wyjaśnienia, przechodzi do innego wątku, który wykorzystuje tylko trywialne przykłady. Miałem nadzieję, że otrzymam tutaj bardziej przejrzystą i kompletną odpowiedź.Polecam zajrzeć do dokumentacji ogólnych symboli wieloznacznych, a zwłaszcza wskazówek dotyczących używania symboli wieloznacznych
Szczerze mówiąc, twoja metoda #doesntCompile
i zadzwoń jak
Jest zasadniczo niepoprawny
Dodajmy prawne wdrożenie:
Jest naprawdę w porządku, ponieważ Double rozszerza liczbę, więc ułożenie
List<Double>
jest absolutnie w porządkuList<Integer>
, prawda?Czy jednak nadal uważasz, że legalne jest przekazywanie tutaj
new HashMap<Integer, List<Integer>>()
ze swojego przykładu?Kompilator tak nie uważa i robi wszystko, aby uniknąć takich sytuacji.
Spróbuj wykonać tę samą implementację za pomocą metody #compile, a kompilator oczywiście nie pozwoli Ci umieścić listy podwójnych na mapie.
Zasadniczo nic nie możesz umieścić, ale
List<T>
dlatego bezpiecznie jest wywoływać tę metodę za pomocąnew HashMap<Integer, List<Integer>>()
lubnew HashMap<Integer, List<Double>>()
lubnew HashMap<Integer, List<Long>>()
lubnew HashMap<Integer, List<Number>>()
.Krótko mówiąc, próbujesz oszukiwać za pomocą kompilatora, który dość dobrze broni się przed takim oszustwem.
Uwaga: odpowiedź wysłana przez Maurice'a Perry'ego jest całkowicie poprawna. Po prostu nie jestem pewien, czy to wystarczająco jasne, więc próbowałem (naprawdę mam nadzieję, że udało mi się) dodać bardziej obszerny post.
źródło