17 votes

Irrégularités avec le type générique joker ( ?)

Je crois que le type ? dans les génériques est un type inconnu spécifique . Ce qui signifie que déclarer, disons, une liste de ce type nous empêcherait d'y ajouter tout type d'objet.

List<?> unknownList;
unknownList.add(new Object()); // This is an error.

Le compilateur donne une erreur comme prévu.

Mais lorsque le type inconnu est un générique de second niveau, le compilateur ne semble pas s'en soucier.

class First<T> {}

List<First<?>> firstUnknownList;

// All these three work fine for some reason.
firstUnknownList.add(new First<>());
firstUnknownList.add(new First<Integer>());
firstUnknownList.add(new First<String>());

Je pensais que le compilateur ne se souciait probablement pas du tout du paramètre générique au deuxième niveau, mais ce n'est pas le cas,

List<First<Integer>> firstIntegerList;
firstIntegerList.add(new First<String>()); // This gives a compiler error as expected.

Alors, pourquoi le compilateur nous permet-il d'ajouter n'importe quel type d'élément alors que seul un élément inconnu (et donc rien) est acceptable dans le deuxième exemple ?

Note : Compilateur Java 1.8

13voto

Andy Turner Points 13883

Vous pouvez ajouter n'importe quoi à un List<T> que vous pouvez stocker dans une référence de type T :

T item = ...
List<T> list = new ArrayList<>();
list.add(item);

First<?> est un supertype de First<T> ; ainsi, vous pouvez stocker une référence à un First<T> dans une variable de type First<?> :

First<?> first = new First<String>();

Donc, en substituant T para First<?> ci-dessus :

First<?> item = new First<String>();
List<First<?>> list = new ArrayList<>();
list.add(item);

Tout ce qui se passe dans l'exemple de l'OP est que la variable temporaire item est omise :

firstUnknownList.add(new First<String>());

Cependant, si vous le faites avec l'option firstIntegerList exemple :

First<Integer> item = new First<String>(); // Compiler error.
List<First<Integer>> list = new ArrayList<>();
list.add(item);

la raison pour laquelle cela n'est pas autorisé est claire : vous ne pouvez pas faire l'affectation de item .


Il est également possible de voir que vous ne pouvez rien faire non sécurisé avec le contenu de cette liste.

Si vous ajoutez quelques méthodes à l'interface :

interface First<T> {
  T producer();
  void consumer(T in);
}

Maintenant, réfléchissez à ce que vous pouvez faire avec les éléments que vous avez ajoutés à la liste :

for (First<?> first : firstUnknownList) {
  // OK at compile time; OK at runtime unless the method throws an exception.
  Object obj = first.producer();

  // OK at compile time; may fail at runtime if null is not an acceptable parameter.
  first.consumer(null);

  // Compiler error - you can't have a reference to a ?.
  first.consumer(/* some maybe non-null value */);
}

Il n'y a donc rien que vous puissiez réellement faire avec les éléments de cette liste qui violerait la sécurité des types (à condition que vous ne fassiez rien de délibéré pour la violer, comme utiliser des types bruts). Vous pouvez démontrer que les méthodes génériques de producteur/consommateur sont pareillement sûres ou interdites par le compilateur.

Il n'y a donc aucune raison pas pour vous permettre de le faire.

4voto

AdamSkywalker Points 4028

Je vais changer First à l'interface Box interface

Box<?> uknownBox boîte grise avec quelque chose dedans

Box<Apple> appleBox boîte avec pomme

List<Box<Apple>> appleBoxList de nombreuses boîtes avec des pommes

List<Box<?>> uknownBoxList de nombreuses boîtes grises inconnues

appleBoxList.add(new Box<Orange>()) - ne peut pas ajouter une boîte avec des oranges à la liste des boîtes de pommes

unknownBoxList.add(new Box<?>()) - nous ne savons pas ce qu'il y a dans ces cases grises, ajouter une case grise inconnue de plus ne change rien

unknownBoxList.add(new Box<Orange>()) - same rules when you add specific boxes
unknownBoxList.add(new Box<Apple>()) - since you are not allowed to 'open' them

unknownBoxList = appleBoxList cela ne compile pas pour empêcher l'ajout de boîtes grises (peut-être pas des pommes) à la liste des boîtes de pommes. à cause de cela, l'opération précédente est légale.

4voto

Marco13 Points 14743

C'est une question de relations entre sous-type et super-types.

List<?> est une liste qui contient des éléments d'un type inconnu (mais spécifique). On ne sait jamais quel type exactement est contenue dans cette liste. Vous ne pouvez donc pas y ajouter d'objets, car ils pourraient être d'un type incorrect :

List<Integer> ints = new ArrayList<Integer>();
List<?> unknowns = ints;

// It this worked, the list would contain a String....
unknowns.add("String"); 

// ... and this would crash with some ClassCastException
Integer i = ints.get(0);

Il peut aussi être clair que vous pouvez faire

List<Number> numbers = null;
Integer integer = null;
numbers.add(integer); 

Cela fonctionne parce que Number est un vrai supertype de Integer . L'ajout d'un objet d'un type plus spécifique à une liste ne viole pas la sécurité de type.


Le point clé concernant le deuxième exemple est le suivant :

First<?> est un supertype de chaque First<T>

Vous pouvez toujours faire quelque chose comme

First<Integer> fInt = null;
First<Integer> fString = null;

First<?> f = null;
f = fInt; // works
f = fString; // works

Donc la raison pour laquelle vous pouvez ajouter un First<String> à un List<First<?>> est la même que celle qui permet d'ajouter un Integer à un List<Number> : L'élément que vous voulez ajouter est d'un vrai sous-type des éléments qui sont attendus dans la liste.

0voto

meriton Points 30447

Je crois que le type ? dans les génériques est un type spécifique inconnu.

C'est légèrement inexact. Oui, un type joker correspond à un type inconnu, mais il peut correspondre à différents types à différents moments :

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

Le seul invariant est qu'une expression dont le type contient un joker donnera toujours une valeur dont le type est conforme à ce joker. Puisque chaque valeur a un type qui n'est pas seulement un joker, on pourrait dire que le joker représente un type (plus) "spécifique" à tout moment.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X