Comment puis-je ajouter à la liste <? étend Nombre> structures de données?

155

J'ai une liste qui est déclarée comme ceci:

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

J'ai essayé d'ajouter 3 à foo3. Cependant, je reçois un message d'erreur comme celui-ci:

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
la source
61
Notez que List<? extends Number>cela ne signifie pas "liste d'objets de différents types, qui s'étendent tous Number". Cela signifie "liste d'objets d'un seul type qui s'étend Number".
Pavel Minaev
1
Vous feriez mieux de vérifier la règle PECS, le producteur étend, le consommateur super. stackoverflow.com/questions/2723397/…
noego

Réponses:

304

Désolé, mais vous ne pouvez pas.

La déclaration générique de List<? extends Number> foo3signifie que la variable foo3peut contenir n'importe quelle valeur d'une famille de types (plutôt que n'importe quelle valeur d'un type spécifique). Cela signifie que l'une de ces cessions est légale:

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

Donc, étant donné cela, quel type d'objet pourriez-vous ajouter à List foo3cela serait légal après l'une des ArrayListaffectations possibles ci-dessus :

  • Vous ne pouvez pas ajouter un Integercar il foo3pourrait pointer vers un fichier List<Double>.
  • Vous ne pouvez pas ajouter un Doublecar il foo3pourrait pointer vers un fichier List<Integer>.
  • Vous ne pouvez pas ajouter un Numbercar il foo3pourrait pointer vers un fichier List<Integer>.

Vous ne pouvez pas ajouter d'objet, List<? extends T>car vous ne pouvez pas garantir le type Listauquel il pointe réellement, vous ne pouvez donc pas garantir que l'objet est autorisé dans cela List. La seule "garantie" est que vous ne pouvez lire qu'à partir de celui-ci et que vous obtiendrez une Tou une sous-classe de T.

La logique inverse s'applique à super, par exemple List<? super T>. Ceux-ci sont légaux:

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

Vous ne pouvez pas lire le type spécifique T (par exemple Number) à partir de List<? super T>parce que vous ne pouvez pas garantir quel type Listil pointe réellement. La seule «garantie» dont vous disposez est que vous pouvez ajouter une valeur de type T(ou toute sous-classe de T) sans violer l'intégrité de la liste pointée.


L'exemple parfait en est la signature pour Collections.copy():

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

Remarquez comment la srcdéclaration de liste utilise extendspour me permettre de passer n'importe quelle liste d'une famille de types de liste liés tout en garantissant qu'elle produira des valeurs de type T ou des sous-classes de T. Mais vous ne pouvez pas ajouter à la srcliste.

La destdéclaration de liste utilise superpour me permettre de transmettre n'importe quelle liste d'une famille de types de liste associés et garantir toujours que je peux écrire une valeur d'un type T spécifique à cette liste. Mais il ne peut pas être garanti de lire les valeurs de type T spécifique si je lis dans la liste.

Alors maintenant, grâce aux jokers génériques, je peux faire n'importe lequel de ces appels avec cette seule méthode:

// 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());

Considérez ce code déroutant et très large pour exercer votre cerveau. Les lignes commentées sont illégales et la raison pour laquelle est indiquée à l'extrême droite de la ligne (besoin de faire défiler pour voir certaines d'entre elles):

  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
la source
11
Pourquoi sommes-nous autorisés à écrire une phrase comme `List <? étend Number> foo3 = new ArrayList <Integer> (); `si nous ne pouvons pas ajouter un élément à cette liste?
Amit Kumar Gupta
8
@articlestack: L'ajout n'est pas la seule chose utile à faire avec une liste. Une fois qu'une liste est remplie, la lecture à partir d'une liste peut être utile. Les jokers génériques permettent d'écrire du code qui fonctionne de manière générique pour une famille de listes, par exemple fonctionne à la fois pour List <Integer> ou List <Double>. Par exemple, regardez la signature de Collection.copy (). L' src Listargument utilise extendspour lire à partir de la liste src, tandis que l' dest Listargument utilise superpour écrire dans la liste dest. Cela permet une méthode qui peut copier depuis List<Integer>ou List<Double>vers List<Number>ou List<Object>.
Bert F
Bert F, désolé pour un commentaire tardif. Y a-t-il une faute de frappe dans votre passage, "pour ajouter une valeur de type T (ou sous-classe de T)" devrait lire (ou superclasse de T)?
Vortex
oui, je l'ai vu aussi @Vortex Je ne sais pas si c'est vrai ou je l'ai mal lu.
Ungeheuer
2
@Vortex - or subclass of Test correct. Par exemple, je ne peux pas ajouter une Object(superclasse de Number) à List<? super Number> foo3car foo3peut avoir été assignée comme: List<? super Number> foo3 = new ArrayList<Number>(qui ne peut contenir que Numberou des sous-classes de Number). <? super Number>fait référence aux types de List<>s qui peuvent être assignés foo3- pas aux types de choses qui peuvent être ajoutées / lues à partir de celui-ci. Les types de choses qui peuvent être ajoutées / supprimées foo3doivent être des choses qui peuvent être ajoutées / supprimées de tout type de ce List<>qui peut être attribué foo3.
Bert F
18

Vous ne pouvez pas (sans lancers dangereux). Vous ne pouvez lire que d'eux.

Le problème est que vous ne savez pas exactement de quoi la liste est une liste. Cela pourrait être une liste de n'importe quelle sous-classe de Number, donc lorsque vous essayez d'y mettre un élément, vous ne savez pas que l'élément s'inscrit réellement dans la liste.

Par exemple, la liste peut être une liste de Bytes, donc ce serait une erreur d'y mettre un Float.

sepp2k
la source
2

"List '<'? Extend Number> est en fait un caractère générique de limite supérieure!

Le caractère générique à limite supérieure indique que toute classe qui étend Number ou Number lui-même peut être utilisée comme type de paramètre formel: Le problème vient du fait que Java ne sait pas quel type est réellement List . Il doit s'agir d'un type EXACT et UNIQUE. J'espère que cela aide :)

andygro
la source
2

Cela m'a dérouté même si j'ai lu les réponses ici, jusqu'à ce que je trouve le commentaire de Pavel Minaev:

Notez que List <? étend Number> ne signifie pas "liste d'objets de différents types, qui étendent tous Number". Cela signifie "liste d'objets d'un seul type qui étend Number"

Après cela, j'ai pu comprendre l'explication géniale de BertF. Liste <? étend le nombre> signifie? pourrait être de n'importe quel type étendant Number (Integer, Double, etc.) et il n'est pas clarifié dans la déclaration (List <? extended Number> list) celui qui d'entre eux il est, donc quand vous voulez utiliser la méthode add, on ne sait pas si l'entrée est du même type ou non; quel est le type du tout?

Donc, les éléments de List <? extend Number> ne pouvait être défini que lors de la construction.

Notez également ceci: lorsque nous utilisons des modèles, nous indiquons au compilateur quel type nous manipulons. T par exemple détient ce type pour nous, mais pas ? fait la même chose

Je dois dire ... C'est l'un des sales à expliquer / apprendre

navid
la source
1

Vous pouvez le faire à la place:

  List<Number> foo3 = new ArrayList<Number>();      
  foo3.add(3);
Ryan Elkins
la source
0

Vous pouvez le fudger en créant une référence à la liste avec un type différent.

(Ce sont les "lancers non sécurisés" mentionnés par 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);

Parce que untypedList, superclassedListet ne trulyclassedListsont que des références à list, vous ajouterez toujours des éléments à la ArrayList d'origine.

Vous n'avez pas besoin de l'utiliser (List<?>)dans l'exemple ci-dessus, mais vous en aurez peut-être besoin dans votre code, selon le type de code qui listvous a été donné.

Notez que l'utilisation ?vous donnera des avertissements du compilateur, jusqu'à ce que vous mettiez ceci au-dessus de votre fonction:

@SuppressWarnings("unchecked")
joeytwiddle
la source
-1

où étendre la liste à partir de 'Object', vous pouvez utiliser list.add et quand vous le souhaitez, utilisez list.get seulement besoin de convertir Object en votre Object;

abbasalim
la source