105 votes

Génériques Java: liste de diffusion impossible <SubClass> lister <SuperClass> ?

Juste rencontrer 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 de Tree.

 public class DataNode implements Tree
 

À ma grande surprise, cela fonctionne pour array:

 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?

117voto

Jon Skeet Points 692016

Ce que vous voyez dans le deuxième cas, c'est la matrice de covariance. C'est une mauvaise chose de l'OMI, ce qui rend les affectations au sein de la matrice dangereux - qu'ils peuvent échouer au moment de l'exécution, en dépit d'être bien au moment de la compilation.

Dans le premier cas, imaginez que le code ne compile, et a été suivie par:

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

Qu'attendez-vous d'arriver?

Vous pouvez faire ceci:

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

... parce qu'alors vous ne pouvez aller chercher les choses d' b1, et ils sont garantis pour être compatible avec Tree. Vous ne pouvez pas appeler b1.add(...) précisément parce que le compilateur ne sait pas si c'est sécuritaire ou non.

Jetez un oeil à cette section de Angelika Langer Java Génériques FAQ pour plus d'informations.

21voto

Matthew Wise Points 221

Si vous devez transtyper de List<DataNode> à List<Tree> , et que vous savez que cela est sans danger, alors un très mauvais moyen pour y parvenir est de procéder à une double distribution:

List<DataNode> a1 = new ArrayList<DataNode>();

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

18voto

Daniel Martin Points 9148

L'explication courte: c'était une erreur de laisser à l'origine pour les Tableaux.

Une longue explication:

Supposons que ce furent autorisés:

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

Alors je n'ai pas pu procéder à:

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!!
}

Maintenant une solution idéale serait de permettre à la nature de la fonte que vous souhaitez lors de l'utilisation d'une variante de l' List qui était en lecture seule, mais ne permettraient pas lors de l'utilisation d'une interface (comme List) que de la lecture-écriture. Java ne permet pas ce genre d'écart de notation sur les génériques des paramètres, ( * ), mais même si c'était le cas vous ne seriez pas en mesure de lancer un List<A> d'un List<B> moins A et B étaient identiques.

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

9voto

List<DataNode> ne pas étendre List<Tree> même si DataNode s'étend Tree. C'est parce que, après votre code que vous pourriez ne b1.ajouter(SomeTreeThatsNotADataNode), et que ce serait un problème depuis a1 aurait un élément qui n'est pas un DataNode.

Vous devez utiliser des caractères génériques pour obtenir quelque chose comme ceci

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

D'autre part DataNode[] NE s'étendent Tree[]. À l'époque, il semblait que 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, quand ils ont ajouté des génériques pour les Collections qu'elle a choisi de le faire un peu différemment pour éviter des erreurs d'exécution.

7voto

sepp2k Points 157757

Lorsque les tableaux ont été conçus (c'est à dire assez bien quand java a été conçu), les développeurs ont décidé que l'écart serait utile, de sorte qu'ils permettaient. Toutefois, cette décision a été souvent critiqué, car il vous permet de le faire (à supposer qu' NotADataNode est une autre sous-classe de la Tree):

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

Ainsi, lorsque les génériques ont été conçus, il a été décidé, que les structures de données générique ne doit permettre explicite de la variance. I. e. vous ne pouvez pas faire List<Tree> b1 = a1;, mais vous pouvez le faire List<? extends Tree> b1 = a1;.

Toutefois, si vous ne l'-ci, essayez d'utiliser l' add ou set méthode (ou toute autre méthode qui prend un T comme argument) provoque une erreur de compilation. De cette manière, il n'est pas possible de faire l'équivalent du tableau ci-dessus problème de compilation (sans dangereux jette).

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