Java - Utiliser le polymorphisme ou les paramètres de type borné

17

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?

HugoMelo
la source
1
Ces deux définitions ne profitent pas beaucoup des paramètres de type. Mais essayez d'écrire addAnimals(List<Animal>)et d'ajouter une liste de chats!
Kilian Foth
7
Eh bien, par exemple, si chacune de vos méthodes ci-dessus renvoie également quelque chose, alors celle qui utilise les génériques peut retourner T, tandis que l'autre ne peut que renvoyer Animal. Donc, pour la personne utilisant ces méthodes, dans le premier cas, il récupérerait exactement ce qu'il voulait: Dod dog = addAnimal (new Dog ()); tandis qu'avec la 2ème méthode, il serait obligé de lancer afin d'obtenir un chien: Chien d = (Chien) addAnimalPoly (nouveau Chien ());
Shivan Dragon
La majorité de mon utilisation des types bornés est le papier peint sur la mauvaise implémentation des génériques de Java (List <T> a des règles de variance différentes de T seul, par exemple). Dans ce cas , il n'y a aucun avantage, il est essentiellement deux façons d'exprimer le même concept, mais comme @ShivanDragon affirme qu'il ne signifie pas que vous avez un T à la place compiletime d'un animal. Vous ne pouvez le traiter que comme un animal en interne, mais vous pouvez l'offrir en tant que T à l'extérieur.
Phoshi

Réponses:

14

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 Animalest autorisé. Dans le second cas, un Collection(ou un sous-type) avec un type générique Animalou 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 Animalet 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).


la source
" Dans le premier cas, seule une collection (ou un sous-type) d'Animal est autorisée. Dans le second cas, une collection (ou un sous-type) avec un type générique d'Animal ou un sous-type est autorisée. " - Voulez-vous doubler- vérifiez votre logique là-bas?
Fund Monica's Lawsuit
3

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 faites confiance à aun 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 garantir 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;)

Rob
la source
2

Cette question est ancienne, mais un facteur important à considérer semble avoir été laissé de côté 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é?"

TL; DR

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.

La réponse complète

Les paramètres de type délimités 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 pet.scratchBelly();ligne, vous indiquant que cette méthode n'est pas définie pour Animal.

prêteur9
la source
1

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 le devez , 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 parviendrait pas à compiler 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.


la source