JavaRush /Blog Java /Random-FR /Caractères génériques dans les génériques Java

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

Publié dans le groupe Random-FR
Bonjour! Nous continuons à étudier le sujet des génériques. Vous avez déjà une bonne quantité de connaissances à leur sujet grâce aux conférences précédentes (sur l'utilisation des varargs lorsque vous travaillez avec des génériques et sur l'effacement de type ), mais nous n'avons pas encore abordé un sujet important : les caractères génériques . C'est une caractéristique très importante des génériques. À tel point que nous lui avons consacré une conférence distincte ! Cependant, il n'y a rien de compliqué avec les jokers, vous le verrez maintenant :) Caractères génériques dans les génériques - 1Regardons un exemple :
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
Que se passe t-il ici? Nous voyons deux situations très similaires. Dans le premier cas, nous essayons de convertir un objet Stringen type Object. Cela ne pose aucun problème, tout fonctionne comme il se doit. Mais dans la deuxième situation, le compilateur renvoie une erreur. Même s’il semblerait que nous fassions la même chose. C'est juste que maintenant nous utilisons une collection de plusieurs objets. Mais pourquoi l’erreur se produit-elle ? Quelle est la différence, essentiellement, si nous convertissons un objet Stringen un type Objectou 20 objets ? Il existe une différence importante entre un objet et une collection d'objets . Si une classe est le successeur d'une classe , alors elle n'est pas un successeur . C'est pour cette raison que nous n'avons pas pu amener le nôtre à . est un héritier , mais n'est pas un héritier . Intuitivement, cela ne semble pas très logique. Pourquoi les créateurs du langage ont-ils été guidés par ce principe ? Imaginons que le compilateur ne nous renvoie pas d'erreur ici : BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
Dans ce cas, nous pourrions par exemple procéder comme suit :
objects.add(new Object());
String s = strings.get(0);
Puisque le compilateur ne nous a pas donné d'erreurs et nous a permis de créer une référence List<Object> objectà une collection de chaînes strings, nous pouvons ajouter stringsnon pas une chaîne, mais simplement n'importe quel objet Object! Ainsi, nous avons perdu la garantie que seuls les objets spécifiés au générique se trouvent dans notre collectionString . Autrement dit, nous avons perdu le principal avantage des génériques : la sécurité. Et comme le compilateur nous a permis de faire tout cela, cela signifie que nous n'obtiendrons qu'une erreur lors de l'exécution du programme, ce qui est toujours bien pire qu'une erreur de compilation. Pour éviter de telles situations, le compilateur nous renvoie une erreur :
// ошибка компиляции
List<Object> objects = strings;
...et lui rappelle qu'il List<String>n'est pas un héritier List<Object>. Il s’agit d’une règle absolue pour le fonctionnement des génériques, et il faut s’en souvenir lors de leur utilisation. Allons-nous en. Disons que nous avons 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()");
   }
}
En tête de la hiérarchie se trouvent simplement les animaux : les animaux de compagnie en sont hérités. Les animaux de compagnie sont divisés en 2 types : les chiens et les chats. Imaginez maintenant que nous devions créer une méthode simple iterateAnimals(). La méthode doit accepter une collection de n'importe quel animal ( Animal, Pet, Cat, Dog), parcourir tous les éléments et afficher quelque chose sur la console à chaque fois. Essayons d'écrire cette méthode :
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Il semblerait que le problème soit résolu ! Cependant, comme nous l'avons découvert récemment, List<Cat>, List<Dog>ou List<Pet>ne sont pas héritiers List<Animal>! Par conséquent, lorsque nous essayons d'appeler une méthode iterateAnimals()avec une liste de chats, nous recevrons une erreur du compilateur :
import java.util.*;

public class Main3 {


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

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

   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());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
La situation ne s’annonce pas bonne pour nous ! Il s'avère que nous devrons écrire des méthodes distinctes pour énumérer tous les types d'animaux ? En fait, non, vous n’êtes pas obligé :) Et les caractères génériques nous y aideront ! Nous allons résoudre le problème selon une méthode simple, en utilisant la construction suivante :
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
C'est le caractère générique. Plus précisément, il s'agit du premier de plusieurs types de caractères génériques - « extends » (un autre nom est Upper Bounded Wildcards ). Que nous dit cette conception ? Cela signifie que la méthode prend en entrée une collection d'objets de classe Animalou d'objets de n'importe quelle classe descendante Animal (? extends Animal). AnimalEn d’autres termes, la méthode peut accepter la collection , Petou Dogcomme entrée Cat– cela ne fait aucune différence. Assurons-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 de la console :

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Nous avons créé un total de 4 collections et 8 objets, et il y a exactement 8 entrées dans la console. Tout fonctionne très bien ! :) Wildcard nous a permis d'intégrer facilement la logique nécessaire avec la liaison à des types spécifiques dans une seule méthode. Nous avons supprimé le besoin d’écrire une méthode distincte pour chaque type d’animal. Imaginez combien de méthodes nous aurions si notre application était utilisée dans un zoo ou une clinique vétérinaire :) Regardons maintenant une situation différente. Notre hiérarchie d'héritage restera la même : la classe de niveau supérieur est Animal, juste en dessous se trouve la classe Pets Pet, et au niveau suivant est Catand Dog. Vous devez maintenant réécrire la méthode iretateAnimals()pour qu'elle puisse fonctionner avec n'importe quel type d'animal, à l'exception des chiens . Collection<Animal>Autrement dit, il doit accepter , Collection<Pet>ou comme entrée Collection<Cat>, mais ne doit pas fonctionner avec Collection<Dog>. Comment pouvons-nous y parvenir? Il semble que la perspective d'écrire une méthode distincte pour chaque type se profile à nouveau devant nous :/ Sinon, comment pouvons-nous expliquer notre logique au compilateur ? Et cela peut être fait très simplement ! Ici, les jokers viendront à nouveau à notre aide. Mais cette fois, nous utiliserons un type différent - « super » (un autre nom est Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Le principe ici est similaire. La construction <? super Cat>indique au compilateur que la méthode iterateAnimals()peut prendre en entrée une collection d'objets de la classe Catou de toute autre classe ancêtre Cat. CatDans notre cas, la classe elle-même , son ancêtre Petset l'ancêtre de l'ancêtre correspondent à cette description Animal. La classe Dogne rentre pas dans cette contrainte, et donc tenter d'utiliser une méthode avec une liste List<Dog>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);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Notre problème est résolu, et encore une fois, les caractères génériques se sont révélés extrêmement utiles :) Ceci conclut la conférence. Vous voyez maintenant à quel point le sujet des génériques est important lors de l'apprentissage de Java - nous y avons consacré 4 conférences ! Mais maintenant vous maîtrisez bien le sujet et pourrez faire vos preuves lors de l'entretien :) Et il est maintenant temps de vous remettre aux tâches ! Bonne chance dans tes études! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION