Jak mogę dodać do listy <? rozszerza liczbę> struktury danych?

154

Mam listę, która jest zadeklarowana w ten sposób:

 List<? extends Number> foo3 = new ArrayList<Integer>();

Próbowałem dodać 3 do foo3. Jednak pojawia się następujący komunikat o błędzie:

The method add(capture#1-of ? extends Number) in the type List<capture#1-of ?
extends Number> is not applicable for the arguments (ExtendsNumber)
unj2
źródło
61
Zauważ, że List<? extends Number>nie oznacza to „listy obiektów różnych typów, z których wszystkie się rozszerzają Number”. Oznacza „listę obiektów jednego typu, które się rozszerzają Number”.
Pavel Minaev
1
Lepiej sprawdź regułę PECS - ciągnie Producent, Super Konsument. stackoverflow.com/questions/2723397/…
noego

Odpowiedzi:

303

Przepraszam, ale nie możesz.

Deklaracja wieloznaczna List<? extends Number> foo3oznacza, że ​​zmienna foo3może zawierać dowolną wartość z rodziny typów (zamiast dowolnej wartości określonego typu). Oznacza to, że którekolwiek z tych zadań są prawnymi:

List<? extends Number> foo3 = new ArrayList<Number>;  // Number "extends" Number
List<? extends Number> foo3 = new ArrayList<Integer>; // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>;  // Double extends Number

Biorąc pod uwagę to, jaki typ obiektu możesz dodać do List foo3tego, byłby legalny po którymkolwiek z powyższych możliwych ArrayListprzypisań:

  • Nie możesz dodać, Integerponieważ foo3może wskazywać na List<Double>.
  • Nie możesz dodać, Doubleponieważ foo3może wskazywać na List<Integer>.
  • Nie możesz dodać, Numberponieważ foo3może wskazywać na List<Integer>.

Nie możesz dodać żadnego obiektu, List<? extends T>ponieważ nie możesz zagwarantować, na jaki rodzaj obiektu Listwskazuje, więc nie możesz zagwarantować, że obiekt jest w tym dozwolony List. Jedyną „gwarancją” jest to, że możesz tylko z niego czytać, a otrzymasz Tpodklasę lub T.

Logika odwrotna dotyczy supernp List<? super T>. Są to legalne:

List<? super Number> foo3 = new ArrayList<Number>; // Number is a "super" of Number
List<? super Number> foo3 = new ArrayList<Object>; // Object is a "super" of Number

Nie możesz odczytać konkretnego typu T (np. Number), List<? super T>Ponieważ nie możesz zagwarantować, na jaki typ Listnaprawdę wskazuje. Jedyną „gwarancją”, jaką masz, jest możliwość dodania wartości typu T(lub dowolnej podklasy T) bez naruszenia integralności wskazywanej listy.


Doskonałym tego przykładem jest podpis Collections.copy():

public static <T> void copy(List<? super T> dest,List<? extends T> src)

Zwróć uwagę, w jaki sposób srcdeklaracja listy extendspozwala mi przekazać dowolną Listę z rodziny pokrewnych typów List i nadal gwarantuje, że wygeneruje wartości typu T lub podklasy T. Ale nie możesz dodawać do srclisty.

Do destzastosowania deklaracji lista superpozwoli mi przekazać żadnej liście z rodziny pokrewnych typów listy i nadal gwarantować mogę napisać wartość określonego typu T do tej listy. Ale nie można zagwarantować, że odczytam wartości określonego typu T, jeśli przeczytam z listy.

Więc teraz, dzięki uniwersalnym symbolom generycznym, mogę wykonać dowolne z tych wywołań za pomocą tej jednej metody:

// copy(dest, src)
Collections.copy(new ArrayList<Number>(), new ArrayList<Number());
Collections.copy(new ArrayList<Number>(), new ArrayList<Integer());
Collections.copy(new ArrayList<Object>(), new ArrayList<Number>());
Collections.copy(new ArrayList<Object>(), new ArrayList<Double());

Rozważ ten zagmatwany i bardzo szeroki kod, aby ćwiczyć mózg. Skomentowane wiersze są nielegalne, a powód jest podany po prawej stronie wiersza (należy przewinąć, aby zobaczyć niektóre z nich):

  List<Number> listNumber_ListNumber  = new ArrayList<Number>();
//List<Number> listNumber_ListInteger = new ArrayList<Integer>();                    // error - can assign only exactly <Number>
//List<Number> listNumber_ListDouble  = new ArrayList<Double>();                     // error - can assign only exactly <Number>

  List<? extends Number> listExtendsNumber_ListNumber  = new ArrayList<Number>();
  List<? extends Number> listExtendsNumber_ListInteger = new ArrayList<Integer>();
  List<? extends Number> listExtendsNumber_ListDouble  = new ArrayList<Double>();

  List<? super Number> listSuperNumber_ListNumber  = new ArrayList<Number>();
//List<? super Number> listSuperNumber_ListInteger = new ArrayList<Integer>();      // error - Integer is not superclass of Number
//List<? super Number> listSuperNumber_ListDouble  = new ArrayList<Double>();       // error - Double is not superclass of Number


//List<Integer> listInteger_ListNumber  = new ArrayList<Number>();                  // error - can assign only exactly <Integer>
  List<Integer> listInteger_ListInteger = new ArrayList<Integer>();
//List<Integer> listInteger_ListDouble  = new ArrayList<Double>();                  // error - can assign only exactly <Integer>

//List<? extends Integer> listExtendsInteger_ListNumber  = new ArrayList<Number>(); // error - Number is not a subclass of Integer
  List<? extends Integer> listExtendsInteger_ListInteger = new ArrayList<Integer>();
//List<? extends Integer> listExtendsInteger_ListDouble  = new ArrayList<Double>(); // error - Double is not a subclass of Integer

  List<? super Integer> listSuperInteger_ListNumber  = new ArrayList<Number>();
  List<? super Integer> listSuperInteger_ListInteger = new ArrayList<Integer>();
//List<? super Integer> listSuperInteger_ListDouble  = new ArrayList<Double>();     // error - Double is not a superclass of Integer


  listNumber_ListNumber.add(3);             // ok - allowed to add Integer to exactly List<Number>

  // These next 3 are compile errors for the same reason:
  // You don't know what kind of List<T> is really
  // being referenced - it may not be able to hold an Integer.
  // You can't add anything (not Object, Number, Integer,
  // nor Double) to List<? extends Number>      
//listExtendsNumber_ListNumber.add(3);     // error - can't add Integer to *possible* List<Double>, even though it is really List<Number>
//listExtendsNumber_ListInteger.add(3);    // error - can't add Integer to *possible* List<Double>, even though it is really List<Integer>
//listExtendsNumber_ListDouble.add(3);     // error - can't add Integer to *possible* List<Double>, especially since it is really List<Double>

  listSuperNumber_ListNumber.add(3);       // ok - allowed to add Integer to List<Number> or List<Object>

  listInteger_ListInteger.add(3);          // ok - allowed to add Integer to exactly List<Integer> (duh)

  // This fails for same reason above - you can't
  // guarantee what kind of List the var is really
  // pointing to
//listExtendsInteger_ListInteger.add(3);   // error - can't add Integer to *possible* List<X> that is only allowed to hold X's

  listSuperInteger_ListNumber.add(3);      // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>
  listSuperInteger_ListInteger.add(3);     // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>
Bert F.
źródło
11
Dlaczego wolno nam napisać zdanie takie jak `Lista <? extends Number> foo3 = new ArrayList <Integer> (); `jeśli nie możemy dalej umieszczać elementu na tej liście?
Amit Kumar Gupta
7
@articlestack: dodawanie nie jest jedyną przydatną rzeczą w przypadku listy. Po zapełnieniu listy przydatne może być czytanie z niej. Generic wildcards pozwalają na pisanie kodu, który działa generalnie dla rodziny list, np. Działa zarówno dla List <Integer> jak i List <Double>. Na przykład spójrz na podpis metody Collection.copy (). Do src Listzastosowania argumentów extendsczytać z listy src, natomiast gdy dest Listużywa argumentów super, aby napisać do dest listy. Pozwala to na jedną metodę, którą można kopiować z List<Integer>lub List<Double>do List<Number>lub List<Object>.
Bert F
Bert F, przepraszam za późny komentarz. Czy w twoim fragmencie jest literówka, powinno się brzmieć „dodać wartość typu T (lub podklasy T)” (lub nadklasy T)?
Vortex
tak, też to widziałem @Vortex Nie jestem pewien, czy to prawda, czy źle przeczytałem.
Ungeheuer
1
@Vortex - or subclass of Tjest poprawne. Na przykład nie mogę dodać klasy Object(nadklasy Number) do, List<? super Number> foo3ponieważ foo3mogły zostać przypisane jako: List<? super Number> foo3 = new ArrayList<Number>(które mogą zawierać tylko Numberlub podklasy Number). <? super Number>odnosi się do typów List<>s, do których można przypisać, a foo3nie do rodzajów rzeczy, które można z niego dodać / odczytać. Rodzaje rzeczy, które można dodać / usunąć, foo3muszą być rzeczami, które można dodać / usunąć z dowolnego rodzaju, do List<>którego można przypisać foo3.
Bert F
17

Nie możesz (bez niebezpiecznych rzutów). Możesz tylko z nich czytać.

Problem polega na tym, że nie wiesz, czego dokładnie jest lista. Może to być lista dowolnej podklasy klasy Number, więc kiedy próbujesz wstawić do niej element, nie wiesz, że element faktycznie pasuje do listy.

Na przykład Lista może być listą Bytes, więc umieszczenie w niej litery a byłoby błędem Float.

sepp2k
źródło
2

„List '<'? Extends Number> jest w rzeczywistości górnym ograniczeniem wieloznacznym!

Symbol wieloznaczny z górną granicą mówi, że każda klasa, która rozszerza samą liczbę lub liczbę, może być użyta jako formalny typ parametru: Problem wynika z faktu, że Java nie wie, jaki typ naprawdę jest List . Musi to być DOKŁADNY i WYJĄTKOWY typ. Mam nadzieję, że to pomoże :)

andygro
źródło
2

To było dla mnie mylące, mimo że czytałem tutaj odpowiedzi, dopóki nie znalazłem komentarza Pawła Minaeva:

Zauważ, że lista <? extends Number> nie oznacza "listy obiektów różnych typów, z których wszystkie rozszerzają Number". Oznacza „listę obiektów jednego typu, która rozszerza liczbę”

Po tym byłem w stanie zrozumieć niesamowite wyjaśnienie BertF. Lista <? rozszerza Number> oznacza? może być dowolnego typu rozszerzającego Number (Integer, Double, itp.) i nie jest jasne w deklaracji (List <? extends Number> list), który z nich to jest, więc kiedy chcesz użyć metody add, nie wiadomo, czy wejście jest tego samego typu lub nie; jaki to w ogóle jest typ?

Więc elementy List <? rozszerza Number> można było ustawić tylko podczas konstruowania.

Zwróć też uwagę na to: kiedy używamy szablonów, mówimy kompilatorowi, z jakim typem się bawimy. T na przykład trzyma ten typ dla nas, ale nie ? robi to samo

Muszę powiedzieć ... To jeden z brudnych do wyjaśnienia / nauczenia

navid
źródło
1

Zamiast tego możesz to zrobić:

  List<Number> foo3 = new ArrayList<Number>();      
  foo3.add(3);
Ryan Elkins
źródło
0

Możesz go sfałszować, tworząc odwołanie do Listy z innym typem.

(To są „niebezpieczne odlewy” wspomniane przez sepp2k.)

List<? extends Number> list = new ArrayList<Integer>();

// This will not compile
//list.add(100);

// WORKS, BUT NOT IDEAL
List untypedList = (List)list;
// It will let you add a number
untypedList.add(200);
// But it will also let you add a String!  BAD!
untypedList.add("foo");

// YOU PROBABLY WANT THIS
// This is safer, because it will (partially) check the type of anything you add
List<Number> superclassedList = (List<Number>)(List<?>)list;
// It will let you add an integer
superclassedList.add(200);
// It won't let you add a String
//superclassedList.add("foo");
// But it will let you add a Float, which isn't really correct
superclassedList.add(3.141);
// ********************
// So you are responsible for ensuring you only add/set Integers when you have
// been given an ArrayList<Integer>
// ********************

// EVEN BETTER
// If you can, if you know the type, then use List<Integer> instead of List<Number>
List<Integer> trulyclassedList = (List<Integer>)(List<?>)list;
// That will prevent you from adding a Float
//trulyclassedList.add(3.141);

System.out.println("list: " + list);

Ponieważ untypedList, superclassedListi trulyclassedListto tylko odniesienia do list, nadal będzie dodawanie elementów do pierwotnego ArrayList.

W rzeczywistości nie musisz używać tego (List<?>)w powyższym przykładzie, ale możesz potrzebować go w swoim kodzie, w zależności od typu, listktóry został Ci dany.

Zauważ, że użycie ?spowoduje ostrzeżenie kompilatora, dopóki nie umieścisz tego nad funkcją:

@SuppressWarnings("unchecked")
joeytwiddle
źródło
-1

gdzie rozszerz listę z 'Object', możesz użyć list.add, a kiedy chcesz użyć list.get, wystarczy rzucić Object na swój obiekt;

abbasalim
źródło