web-dev-qa-db-fra.com

Java Generics: Impossible de convertir List <SubClass> en List <SuperClass>?

Venez avec ce problème:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // compile error: incompatible type

Où le type DataNode est un sous-type d'arbre.

public class DataNode implements Tree

À ma grande surprise, cela fonctionne pour le tableau:

DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2;   // this is okay

Cela aime un peu étrange. Quelqu'un peut-il donner une explication à ce sujet?

101
SiLent SoNG

Ce que vous voyez dans le deuxième cas est la covariance du tableau . C'est une mauvaise chose IMO, qui rend les affectations au sein du tableau dangereuses - elles peuvent échouer au moment de l'exécution, bien qu'elles soient correctes au moment de la compilation.

Dans le premier cas, imaginez que le code a compilé et a été suivi par:

b1.add(new SomeOtherTree());
DataNode node = a1.get(0);

À quoi vous attendriez-vous?

Tu peux le faire:

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;

... car alors vous ne pouvez récupérer que les éléments de b1, et ils sont garantis compatibles avec Tree. Vous ne pouvez pas appeler b1.add(...) précisément parce que le compilateur ne saura pas si c'est sûr ou non.

Jetez un œil à cette section de la Java Generics FAQ d'Angelika Langer pour plus d'informations.

114
Jon Skeet

La courte explication: c'était une erreur de le permettre à l'origine pour les tableaux.

L'explication plus longue:

Supposons que cela soit autorisé:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // pretend this is allowed

Alors je ne pouvais pas continuer:

b1.add(new TreeThatIsntADataNode()); // Hey, b1 is a List<Tree>, so this is fine

for (DataNode dn : a1) {
  // Uh-oh!  There's stuff in a1 that isn't a DataNode!!
}

Désormais, une solution idéale permettrait le type de transtypage souhaité lors de l'utilisation d'une variante de List qui était en lecture seule, mais la refuserait lors de l'utilisation d'une interface (comme List) en lecture-écriture . Java ne permet pas ce type de notation de variance sur les paramètres génériques, (*) mais même si c'était le cas, vous ne seriez pas en mesure de lancer un List<A> à un List<B> à moins que A et B soient identiques.

(*) Autrement dit, ne le permet pas lors de l'écriture de cours. Vous pouvez déclarer que votre variable a le type List<? extends Tree>, et c'est très bien.

16
Daniel Martin

List<DataNode> Ne s'étend pas List<Tree> Même si DataNode étend Tree. C'est parce qu'après votre code, vous pouvez faire b1.add (SomeTreeThatsNotADataNode), et ce serait un problème car alors a1 aurait un élément qui n'est pas un DataNode.

Vous devez utiliser un caractère générique pour réaliser quelque chose comme ça

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error

D'un autre côté, DataNode[] PROLONGE Tree[]. À l'époque, cela semblait être la chose logique à faire, mais vous pouvez faire quelque chose comme:

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2; // this is okay
b2[0] = new Tree(); // this will cause ArrayStoreException since b2 is actually a DataNode[] and can't store a Tree

C'est pourquoi, lorsqu'ils ont ajouté des génériques aux collections, ils ont choisi de le faire un peu différemment pour éviter les erreurs d'exécution.

9
Andrei Fierbinteanu

Lorsque les tableaux ont été conçus (c'est-à-dire à peu près lorsque Java a été conçu), les développeurs ont décidé que la variance serait utile, ils l'ont donc autorisée. Cependant, cette décision a souvent été critiquée car elle vous permet de le faire ( supposons que NotADataNode est une autre sous-classe de Tree):

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2;   // this is okay
b2[0] = new NotADataNode(); //compiles fine, causes runtime error

Ainsi, lors de la conception des génériques, il a été décidé que les structures de données génériques ne devraient permettre qu'une variance explicite. C'est à dire. tu ne peux pas faire List<Tree> b1 = a1;, mais vous pouvez faire List<? extends Tree> b1 = a1;.

Cependant, si vous faites ce dernier, essayer d'utiliser la méthode add ou set (ou toute autre méthode qui prend un T comme argument) provoquera une erreur de compilation. De cette façon, il n'est pas possible de compiler l'équivalent du problème de tableau ci-dessus (sans transtypages dangereux).

7
sepp2k

Réponse courte: la liste a1 n'est pas du même type que la liste b2; En a1, vous pouvez mettre n'importe quel type d'objet qui étend DataNode. Il peut donc contenir d'autres types que Tree.

2
tjin

DataNode peut être un sous-type d'arborescence, mais List DataNode n'est pas un sous-type d'arborescence de liste.

https://docs.Oracle.com/javase/tutorial/extra/generics/subtype.html

1
BobTurbo

C'est la réponse de C #, mais je pense que cela n'a pas vraiment d'importance ici, car la raison est la même.

"En particulier, contrairement aux types de tableau, les types de référence construits ne présentent pas de conversions" covariantes ". Cela signifie qu'un type List <B> n'a pas de conversion (implicite ou explicite) en List <A> même si B est dérivé de A. De même, aucune conversion n'existe de List <B> à List <object>.

La raison en est simple: si une conversion en Liste <A> est autorisée, alors apparemment on peut stocker des valeurs de type A dans la liste. Mais cela romprait l'invariant selon lequel chaque objet d'une liste de type List <B> est toujours une valeur de type B, sinon des échecs inattendus peuvent se produire lors de l'affectation dans des classes de collection. "

http://social.msdn.Microsoft.com/forums/en-US/clr/thread/22e262ed-c3f8-40ed-baf3-2cbcc54a216e

1
Draco Ater

Il s'agit d'un problème classique avec les génériques implémentés avec l'effacement de type.

Supposons que votre premier exemple ait vraiment fonctionné. Vous pourrez alors effectuer les opérations suivantes:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // suppose this works
b1.add(new Tree());

Mais depuis b1 et a1 fait référence au même objet, cela signifie que a1 fait maintenant référence à un List qui contient à la fois DataNodes et Trees. Si vous essayez d'obtenir ce dernier élément, vous obtiendrez une exception (je ne me souviens pas lequel).

0
lindelof