CodeGym /Blog Java /Random-FR /Caractères génériques dans les génériques
Auteur
Artem Divertitto
Senior Android Developer at United Tech

Caractères génériques dans les génériques

Publié dans le groupe Random-FR
Salut! Continuons notre étude des génériques. Vous avez déjà acquis un ensemble substantiel de connaissances à leur sujet grâce aux leçons précédentes (sur l'utilisation de varargs lorsque vous travaillez avec des génériques et sur l'effacement de type ), mais il y a un sujet important que nous n'avons pas encore abordé : les jokers . C'est une caractéristique très importante des génériques. À tel point que nous lui avons consacré une leçon à part ! Cela dit, il n'y a rien de particulièrement compliqué à propos des jokers. Vous le verrez tout de suite :) Caractères génériques dans les génériques - 1Prenons un exemple :

public class Main {

   public static void main(String[] args) {
      
       String str = new String("Test!");
       // No problem
       Object obj = str;
      
       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
Que se passe t-il ici? Nous voyons deux situations très similaires. Dans le cas, nous transtypons un Stringobjet en Objectobjet. Il n'y a pas de problèmes ici - tout fonctionne comme prévu. Mais dans la deuxième situation, le compilateur génère une erreur. Mais nous faisons la même chose, n'est-ce pas ? Cette fois, nous utilisons simplement une collection de plusieurs objets. Mais pourquoi l'erreur se produit-elle ? Quelle est la différence? Allons-nous lancer un Stringobjet vers un Objectou 20 objets ? Il existe une distinction importante entre un objet et une collection d'objets . Si la Bclasse est un enfant de la Aclasse, alors Collection<B>n'est pas un enfant de Collection<A>. C'est pourquoi nous n'avons pas pu lancer notre List<String>à unList<Object>. Stringest un enfant de Object, mais List<String>n'est pas un enfant de List<Object>. Cela peut ne pas sembler super intuitif. Pourquoi les créateurs de la langue en sont-ils arrivés là ? Imaginons que le compilateur ne nous renvoie pas d'erreur :

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
Dans ce cas, nous pourrions, par exemple, faire ce qui suit :

objects.add(new Object());
String s = strings.get(0);
Parce que le compilateur ne nous a donné aucune erreur et nous a permis de créer une List<Object>référence qui pointe vers strings, nous pouvons ajouter n'importe quel ancien Objectobjet à la stringscollection ! Ainsi, nous avons perdu la garantie que notre collection contient uniquement les Stringobjets spécifiés par l'argument type dans l'appel de type générique . En d'autres termes, nous avons perdu le principal avantage des génériques - la sécurité des types. Et comme le compilateur ne nous a pas empêché de le faire, nous n'obtiendrons une erreur qu'au moment de l'exécution, ce qui est toujours bien pire qu'une erreur de compilation. Pour éviter de telles situations, le compilateur nous renvoie une erreur :

// Compilation error
List<Object> objects = strings;
...et nous rappelle que List<String>n'est pas un descendant de List<Object>. Il s'agit d'une règle absolue pour les génériques, et il faut s'en souvenir lorsque l'on travaille avec eux. Allons-nous en. Supposons que nous ayons une petite hiérarchie de classes :

public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
La hiérarchie est surmontée d'une simple classe Animal, qui est héritée par Pet. Pet a 2 sous-classes : Chien et Chat. Supposons maintenant que nous devions créer une iterateAnimals()méthode simple. La méthode doit prendre une collection de tous les animaux ( Animal, Pet, Cat, Dog), itérer sur tous les éléments et afficher un message sur la console à chaque itération. Essayons d'écrire une telle méthode :

public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Il semble que le problème soit résolu ! Cependant, comme nous l'avons appris récemment, List<Cat>, List<Dog>et List<Pet>ne sont pas des descendants de List<Animal>! Cela signifie que lorsque nous essayons d'appeler la iterateAnimals()méthode avec une liste de chats, nous obtenons une erreur de compilation :

import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       // Compilation error!
       iterateAnimals(cats);
   }
}
La situation ne s'annonce pas très bonne pour nous ! Devons-nous écrire des méthodes distinctes pour énumérer chaque type d'animal ? En fait, non, nous ne le faisons pas :) Et il se trouve que les jokers nous aident avec ça ! Nous pouvons résoudre le problème avec une méthode simple en utilisant la construction suivante :

public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Il s'agit d'un joker. Plus précisément, il s'agit du premier de plusieurs types de caractères génériques. Il est connu sous le nom de caractères génériques à limite supérieure et est exprimé par ? s'étend . Que nous dit cette construction ? Cela signifie que la méthode accepte une collection d' Animalobjets ou une collection d'objets de n'importe quelle classe qui descend de Animal(? étend Animal). En d'autres termes, la méthode peut accepter une collection d'objets Animal, Pet, Dogou Cat- cela ne fait aucune différence. Convainquons-nous que cela fonctionne :

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Sortie console :

Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Nous avons créé un total de 4 collections et 8 objets, et il y a exactement 8 entrées sur la console. Tout fonctionne très bien ! :) Le caractère générique nous a permis d'intégrer facilement la logique nécessaire liée à des types spécifiques dans une seule méthode. Nous avons éliminé la nécessité d'écrire une méthode distincte pour chaque type d'animal. Imaginez le nombre de méthodes dont nous aurions eu besoin si notre application avait été utilisée par un zoo ou un cabinet vétérinaire :) Mais examinons maintenant une situation différente. Notre hiérarchie d'héritage reste inchangée : la classe de niveau supérieur est Animal, avec la Petclasse juste en dessous, et les classes Catet Dogau niveau suivant. Maintenant, vous devez réécrire la iterateAnimals()méthode pour qu'elle fonctionne avec n'importe quel type d'animal, à l'exception des chiens . C'est-à-dire qu'il devrait accepter Collection<Animal>,Collection<Pet>ou Collection<Car>, mais cela ne devrait pas fonctionner avec Collection<Dog>. Comment pouvons-nous y parvenir? Il semble que nous soyons à nouveau confrontés à la perspective d'écrire une méthode distincte pour chaque type :/ Sinon, comment expliquons-nous au compilateur ce que nous voulons qu'il se passe ? C'est en fait assez simple ! Encore une fois, les jokers viennent ici à notre aide. Mais cette fois, nous utiliserons un autre type de caractère générique — un caractère générique à limite inférieure , qui est exprimé à l'aide de super .

public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Another iteration in the loop!");
   }
}
Ici le principe est similaire. La <? super Cat>construction indique au compilateur que la iterateAnimals()méthode peut accepter en entrée une collection d' Catobjets ou tout ancêtre de la Catclasse en entrée. Dans ce cas, la Catclasse, son parent Petet le parent de son parent Animalcorrespondent tous à cette description. La Dogclasse ne correspond pas à notre restriction, donc une tentative d'utilisation de la méthode avec un List<Dog>argument entraînera une erreur de compilation :

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
  
   // Compilation error!
   iterateAnimals(dogs);
}
Nous avons résolu notre problème et, une fois de plus, les caractères génériques se sont avérés extrêmement utiles :) Avec cela, la leçon est terminée. Vous voyez maintenant à quel point les génériques sont importants dans votre étude de Java - nous avons eu 4 leçons entières à leur sujet ! Mais maintenant, vous maîtrisez bien le sujet et vous pouvez prouver vos compétences lors d'entretiens d'embauche :) Et maintenant, il est temps de revenir aux tâches ! Bonne réussite dans vos études ! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION