Supposons que j'ai cette hiérarchie de classe ...
public abstract class Animal {
public abstract void eat();
public abstract void talk();
}
class Dog extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
class Cat extends Animal {
@Override
public void eat() {
}
@Override
public void talk() {
}
}
Et puis j'ai ...
public static <T extends Animal> void addAnimal(T animal) {
animal.eat();
animal.talk();
}
public static void addAnimalPoly(Animal animal) {
animal.eat();
animal.talk();
}
Quelle est la différence lors de l'utilisation de paramètres de type borné ou de polymorphisme?
Et quand utiliser l'un ou l'autre?
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 type à un autre type
Ces deux signatures de méthode finissent par être les mêmes dans le code d'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 un sous-type) de Animal
est autorisé. Dans le second cas, un Collection
(ou sous-type) avec un type générique de 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 des objets
L'autre fois, c'est important avec 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 renvoyer Animal
et vous auriez besoin d'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).
Utilisez des génériques au lieu de downcasting. Le "downcasting" est mauvais, passant d'un type plus général à un type plus spécifique:
Animal a = hunter.captureOne();
Cat c = (Cat)a; // ACK!!!!!! What if it's a Dog? ClassCastException!
... vous êtes sûr que a
est un chat, mais le compilateur ne peut pas le garantir. Il pourrait s'avérer être un chien lors de l'exécution.
Voici où vous utiliseriez des génériques:
public class <A> Hunter() {
public A captureOne() { ... }
}
Vous pouvez maintenant spécifier que vous voulez un chasseur de chats:
Hunter<Cat> hunterC = new Hunter<Cat>();
Cat c = hunterC.captureOne();
Hunter<Dog> hunterD = new Hunter<Dog>();
Dog d = hunterD.captureOne();
Maintenant, le compilateur peut garantie que hunterC ne capturera que les chats, et hunterD ne capturera que les chiens.
Il vous suffit donc d'utiliser un polymorphisme régulier si vous souhaitez simplement gérer des classes spécifiques comme type de base. L'upcasting est une bonne chose. Mais si vous vous trouvez dans une situation où vous devez gérer des classes spécifiques comme leur propre type, génériquement, utilisez des génériques.
Ou, vraiment, si vous constatez que vous devez abattre, utilisez des génériques.
EDIT: le cas le plus général est lorsque vous voulez différer la décision des types de types à gérer. Ainsi, les types deviennent un paramètre, ainsi que les valeurs.
Disons que je veux que ma classe Zoo gère les chats ou les éponges. Je n'ai pas de super classe commune. Mais je peux toujours utiliser:
public class <T> Zoo() { ... }
Zoo<Sponge> spongeZoo = ...
Zoo<Cat> catZoo = ...
le degré de verrouillage dépend de ce que vous essayez de faire;)
Cette question est ancienne, mais un facteur important à considérer semble avoir été omis quant au moment d'utiliser le polymorphisme par rapport aux paramètres de type borné. Ce facteur peut être légèrement tangent à l'exemple donné dans la question mais, je pense, très pertinent pour le plus général "Quand utiliser le polymorphisme vs les paramètres de type borné?"
Si vous vous retrouvez à déplacer du code d'une sous-classe vers une classe de base contre votre meilleur jugement, en raison de l'impossibilité d'y accéder de manière polymorphe, les paramètres de type bornés pourraient être une solution potentielle.
Les paramètres de type délimité peuvent exposer des méthodes de sous-classe concrètes et non héritées pour une variable de membre héritée. Le polymorphisme ne peut pas
Pour élaborer en étendant votre exemple:
public abstract class AnimalOwner<T extends Animal> {
protected T pet;
public abstract void rewardPet();
}
// Modify the dog class
class Dog extends Animal {
// ...
// This method is not inherited from anywhere!
public void scratchBelly() {
System.out.println("Belly: Scratched");
}
}
class DogOwner extends AnimalOwner<Dog> {
DogOwner(Dog dog) {
this.pet = dog;
}
@Override
public void rewardPet()
{
// ---- Note this call ----
pet.scratchBelly();
}
}
Si la classe abstraite AnimalOwner a été définie pour avoir un protected Animal pet;
Et opter pour le polymorphisme, le compilateur affichera une erreur sur la ligne pet.scratchBelly();
, vous indiquant que cette méthode n'est pas définie pour Animal.
Dans votre exemple, vous n'utilisez pas (et ne devriez pas) utiliser de type borné. N'utilisez des paramètres de type borné que lorsque vous devez le faire, car ils sont plus déroutants à comprendre.
Voici quelques situations où vous utiliserez des paramètres de type borné:
Paramètres des collections
class Zoo {
private List<Animal> animals;
public void add(Collection<? extends Animal> newAnimals) {
animals.addAll(newAnimals);
}
}
alors vous pouvez appeler
List<Dog> dogs = ...
Zoo.add(dogs);
Zoo.add(dogs)
ne pourrait pas être compilé sans <? extends Animal>
, car les génériques ne sont pas covariants.
Sous-classement
abstract class Warrior<T extends Weapon> {
public abstract T getWeapon();
}
pour limiter le type de sous-classe peut fournir.
Vous pouvez également utiliser plusieurs limites <T extends A1 & A2 & A3>
Pour vous assurer qu'un type est un sous-type de tous les types de la liste.