ceci provient de HeadFirst Java : (page 575)
Ce:
public <T extends Animal> void takeThing(ArrayList<T> list)
Fait la même chose que ça:
public void takeThing(ArrayList<? extends Animal> list)
Voici donc ma question: s’ils sont exactement les mêmes, pourquoi ne pas écrire
public <? extends Animal> void takeThing(ArrayList<?> list)
ou
public void takeThing(ArrayList<T extends Animal> list)
Aussi, quand serait-il utile d'utiliser un? au lieu d'un T dans une déclaration de méthode (comme ci-dessus) avec Generics ou pour une déclaration de classe? Quels sont les bénéfices?
La grande différence entre
public <T extends Animal> void takeThing(ArrayList<T> list)
et
public void takeThing(ArrayList<? extends Animal> list)
est-ce que dans la méthode précédente, vous pouvez faire référence à "T" dans la méthode en tant que classe concrète donnée. Dans la deuxième méthode, vous ne pouvez pas faire cela.
Voici un exemple plus complexe pour illustrer ceci:
// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
Map<T, String> names = new HashMap<T, String>();
for (T animal : list) {
names.put(animal, animal.getName()); // i assume there is a getName method
}
return names;
}
// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
Map<Animal, String> names = new HashMap<Animal, String>();
for (Animal animal : list) {
names.put(animal, animal.getName()); // i assume there is a getName method
}
return names;
}
Avec la première méthode, si vous passez une liste de chats, vous obtenez une carte avec Cat comme clé. La deuxième méthode renverrait toujours une carte avec une clé animale générale.
Au fait, ce n'est pas une syntaxe Java valide:
public <? extends Animal> void takeThing(ArrayList<?> list)
En utilisant cette forme de déclaration de méthode générique, vous devez utiliser un identifiant Java valide et non "?".
Modifier:
Le formulaire "? Extend Type" s'applique uniquement à la déclaration du type de variable ou de paramètre. Dans une déclinaison de méthode générique, il doit s'agir d'un "identificateur étendu de type", car vous pouvez vous référer à "l'identificateur" à partir de votre méthode.
Les wild cards concernent la variance des génériques. Je vais essayer de préciser ce que cela signifie en fournissant des exemples.
Fondamentalement, cela est lié au fait que pour les types S et T, où S est un sous-type de T, un type générique G<S>
n'est pas un sous-type valide de G<T>
.
List<Number> someNumbers = new ArrayList<Long>(); // compile error
Vous pouvez remédier à cela avec des wild cards
List<? extends Number> someNumbers = new ArrayList<Long>(); // this works
Veuillez noter que vous ne pouvez rien mettre dans une telle liste
someNumbers.add(2L); //compile error
même (et plus surprenant pour de nombreux développeurs):
List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!
Je pense que SO n'est pas le bon endroit pour discuter de cela en détail. Je vais essayer de trouver quelques articles et articles expliquant cela plus en détail.
Lier le type à un paramètre de type peut être plus puissant, en fonction de ce que la méthode est censée faire. Je ne suis pas sûr de ce que takeThing
est censé faire, mais imaginez que nous ayons une méthode avec l'une de ces signatures de type:
public <T extends Animal> void foo(ArrayList<T> list);
//or
public void foo(ArrayList<? extends Animal> list);
Voici un exemple concret de ce que vous ne pouvez faire qu'avec la signature de type first:
public <T extends Animal> void foo(ArrayList<T> list) {
list.add(list.remove(0)); // (cycle front element to the back)
}
Dans ce cas, T
est requis pour informer le vérificateur de type que l'élément qui est retiré de la liste est un élément OK pour ajouter de la liste.
Vous ne pouvez pas faire cela avec un caractère générique car, comme le caractère générique n'a pas été lié à un paramètre de type, son contexte n'est pas suivi (eh bien, il est suivi via des "captures", mais il n'est pas disponible pour en tirer parti). Vous pouvez obtenir plus d'informations à ce sujet dans une autre réponse que j'ai donnée: Comment fonctionnent les génériques?
Si vous écrivez ? extends T
vous dites "tout ce qui est un T ou plus spécifique". Par exemple: un List<Shape>
ne peut contenir que Shape
s, alors qu'un List<? extends Shape>
peut avoir Shape
s, Circle
s, Rectangle
s, etc.
Si vous écrivez ? super T
, vous dites "tout ce qui est un T ou plus général". Ceci est moins souvent utilisé, mais a ses cas d'utilisation. Un exemple typique serait un rappel: si vous voulez renvoyer un Rectangle
à un rappel, vous pouvez utiliser Callback<? super Rectangle>
, puisqu'un Callback<Shape>
sera capable de gérer également Rectangle
s.
Voici l'article de Wikipedia pertinent .
Si votre méthode takeThing
doit ajouter des éléments au paramètre list
, la version générique ne sera pas compilée.
Le cas intéressant est lorsque vous n’ajoutez pas à la liste et que les deux versions semblent compiler et fonctionner.
Dans ce cas, vous écririez la version générique lorsque vous souhaitez autoriser différents types d'animaux dans la liste (plus de souplesse) et la version du paramètre lorsque vous avez besoin d'un type d'animal fixe dans la liste: le type T.
Par exemple, le Java.util.Collection
déclare:
interface Collection<E> {
...
public boolean containsAll(Collection<?> c);
...
}
Et supposons que vous ayez le code suivant:
Collection<Object> c = Arrays.<Object>asList(1, 2);
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3);
i.containsAll(c); //compiles and return true as expected
Si le Java.util.Collection
serait:
interface Collection<E> {
...
public boolean containsAll(Collection<E> c);
...
}
Le code de test ci-dessus ne serait pas compilé et la flexibilité de l'API Collection
serait réduite.
Il convient de noter que la dernière définition de containsAll
présente l'avantage d'attraper plus d'erreurs au moment de la compilation, par exemple:
Collection<String> c = Arrays.asList("1", "2");
Collection<Integer> i = Arrays.asList(1, 2, 3);
i.containsAll(c); //does not compile, the integer collection can't contain strings
Mais manque le test valide avec un Collection<Object> c = Arrays.<Object>asList(1, 2);
L'utilisation des caractères génériques Java Generics est régie par le principe GET-PUT (également appelé principe IN-OUT). Cela stipule que: Utilisez un caractère générique "étend" lorsque vous obtenez uniquement des valeurs d'une structure, Utilisez un caractère générique "super" lorsque vous ne placez que des valeurs dans une structure et n'utilisez pas de caractère générique lorsque vous effectuez les deux. Ceci ne s'applique pas au type de retour d'une méthode. N'utilisez pas de caractère générique comme type de retour . Voir exemple ci-dessous:
public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}