Ces deux exemples sont équivalents et, en fait, seront compilés dans le même bytecode.
Il y a deux façons d'ajouter un type générique borné à une méthode comme dans votre premier exemple fera n'importe quoi.
Passer le paramètre de type à un autre type
Ces deux signatures de méthode finissent par être les mêmes dans le code octet, mais le compilateur applique la sécurité des types:
public static <T extends Animal> void addAnimals(Collection<T> animals)
public static void addAnimals(Collection<Animal> animals)
Dans le premier cas, seul un Collection
(ou sous-type) de Animal
est autorisé. Dans le second cas, un Collection
(ou un sous-type) avec un type générique Animal
ou un sous-type est autorisé.
Par exemple, ce qui suit est autorisé dans la première méthode mais pas dans la seconde:
List<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
addAnimals(cats);
La raison en est que la seconde autorise uniquement les collections d'animaux, tandis que la première autorise les collections de tout objet attribuable à l'animal (c'est-à-dire les sous-types). Notez que si cette liste était une liste d'animaux qui contenaient un chat, l'une ou l'autre méthode l'accepterait: le problème est la spécification générique de la collection, pas ce qu'elle contient réellement.
Retour d'objets
L'autre fois que cela compte, c'est le retour des objets. Supposons que la méthode suivante existe:
public static <T extends Animal> T feed(T animal) {
animal.eat();
return animal;
}
Vous seriez en mesure de faire ce qui suit avec lui:
Cat c1 = new Cat();
Cat c2 = feed(c1);
Bien qu'il s'agisse d'un exemple artificiel, il y a des cas où cela a du sens. Sans génériques, la méthode devrait revenir Animal
et vous devrez ajouter un transtypage de type pour le faire fonctionner (ce que le compilateur ajoute au code d'octets de toute façon dans les coulisses).
addAnimals(List<Animal>)
et d'ajouter une liste de chats!