J'essaie simplement de comprendre le mot clé extends
dans Java Generics.
List<? extends Animal>
signifie que nous pouvons bourrer n'importe quel objet dans le List
qui IS AAnimal
alors ce qui suit ne signifie pas la même chose:
List<Animal>
Quelqu'un peut-il m'aider à connaître la différence entre les deux ci-dessus? Pour moi, extends
semble juste redondant ici.
Merci!
List<Dog>
est un sous-type de List<? extends Animal>
, mais pas un sous-type de List<Animal>
.
Pourquoi est-ce List<Dog>
pas un sous-type de List<Animal>
? Prenons l'exemple suivant:
void mySub(List<Animal> myList) {
myList.add(new Cat());
}
Si vous étiez autorisé à passer un List<Dog>
à cette fonction, vous obtiendrez une erreur d'exécution.
EDIT: Maintenant, si nous utilisons List<? extends Animal>
à la place, les événements suivants se produiront:
void mySub(List<? extends Animal> myList) {
myList.add(new Cat()); // compile error here
Animal a = myList.get(0); // works fine
}
Vous pourriez passer un List<Dog>
à cette fonction, mais le compilateur se rend compte que l'ajout de quelque chose à la liste pourrait vous poser des problèmes. Si vous utilisez super
au lieu de extends
(vous permettant de passer un List<LifeForm>
), C'est l'inverse.
void mySub(List<? super Animal> myList) {
myList.add(new Cat()); // works fine
Animal a = myList.get(0); // compile error here, since the list entry could be a Plant
}
La théorie derrière cela est Co- et Contravariance .
Je vois que vous avez déjà accepté une réponse, mais je voudrais simplement ajouter mon point de vue, car je pense que je peux vous être utile ici.
La différence entre List<Animal>
et List<? extends Animal>
est comme suit.
Avec List<Animal>
, vous savez ce que vous avez est certainement une liste d'animaux. Il n'est pas nécessaire qu'ils soient tous exactement des animaux - ils peuvent également être des types dérivés. Par exemple, si vous avez une liste d'animaux, il est logique qu'un couple puisse être des chèvres, et certains d'entre eux des chats, etc. - non?
Par exemple, cela est totalement valable:
List<Animal> aL= new List<Animal>();
aL.add(new Goat());
aL.add(new Cat());
Animal a = aL.peek();
a.walk(); // assuming walk is a method within Animal
Bien sûr, ce qui suit ne serait pas valide:
aL.peek().meow(); // we can't do this, as it's not guaranteed that aL.peek() will be a Cat
Avec List<? extends Animal>
, vous faites une déclaration sur le type de liste avec lequel vous avez affaire.
Par exemple:
List<? extends Animal> L;
Ce n'est en fait pas une déclaration du type d'objet que L peut contenir . C'est une déclaration sur les types de listes que L peut référencer.
Par exemple, nous pourrions faire ceci:
L = aL; // remember aL is a List of Animals
Mais maintenant, tout ce que le compilateur sait sur L, c'est qu'il s'agit d'une liste de [soit un animal, soit un sous-type d'animal]
Alors maintenant, ce qui suit n'est pas valide:
L.add(new Animal()); // throws a compiletime error
Parce que pour tout ce que nous savons, L pourrait faire référence à une liste de chèvres - à laquelle nous ne pouvons pas ajouter un animal.
Voici pourquoi:
List<Goat> gL = new List<Goat>(); // fine
gL.add(new Goat()); // fine
gL.add(new Animal()); // compiletime error
Dans ce qui précède, nous essayons de faire d'un animal une chèvre. Cela ne fonctionne pas, car si après avoir fait cela, nous essayions de faire faire à cet animal un "coup de tête", comme le ferait une chèvre? Nous ne savons pas nécessairement que l'Animal peut le faire.
Ce n'est pas. List<Animal>
indique que la valeur affectée à cette variable doit être de "type" List<Animal>
. Cela ne signifie cependant pas qu'il ne doit y avoir que des objets Animal
, il peut aussi y avoir des sous-classes.
List<Number> l = new ArrayList<Number>();
l.add(4); // autoboxing to Integer
l.add(6.7); // autoboxing to Double
Vous utilisez le List<? extends Number>
construire si vous êtes intéressé par une liste qui a obtenu Number
objets, mais l'objet List lui-même n'a pas besoin d'être de type List<Number>
mais peut toute autre liste de sous-classes (comme List<Integer>
).
Ceci est parfois utilisé pour les arguments de méthode pour dire "Je veux une liste de Numbers
, mais je m'en fiche si c'est juste List<Number>
, il peut s'agir d'un List<Double>
too ". Cela évite les transtypages étranges si vous avez une liste de certaines sous-classes, mais la méthode attend une liste de la classe de base.
public void doSomethingWith(List<Number> l) {
...
}
List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // not working
Cela ne fonctionne pas comme prévu List<Number>
, pas un List<Double>
. Mais si vous avez écrit List<? extends Number>
tu peux passer List<Double>
objets même s'ils ne le sont pas List<Number>
objets.
public void doSomethingWith(List<? extends Number> l) {
...
}
List<Double> d = new ArrayList<Double>();
doSomethingWith(d); // works
Remarque: Tout cela n'est pas lié à l'héritage des objets dans la liste elle-même. Vous pouvez toujours ajouter des objets Double
et Integer
dans un List<Number>
liste, avec ou sans ? extends
des trucs.