CodeGym/Blog Java/Random-FR/Génériques en Java
Auteur
Volodymyr Portianko
Java Engineer at Playtika

Génériques en Java

Publié dans le groupe Random-FR
membres
Salut! Nous allons parler des génériques Java. Je dois dire que vous apprendrez beaucoup ! Non seulement cette leçon, mais aussi les prochaines leçons, seront consacrées aux génériques. Donc, si vous êtes intéressé par les génériques, c'est aujourd'hui votre jour de chance : vous en apprendrez beaucoup sur les fonctionnalités des génériques. Et sinon, résignez-vous et détendez-vous ! :) C'est un sujet très important, et vous devez le savoir. Commençons par le plus simple : le "quoi" et le "pourquoi".

Que sont les génériques Java ?

Les génériques sont des types qui ont un paramètre. Lors de la création d'un type générique, vous spécifiez non seulement un type, mais également le type de données avec lequel il fonctionnera. Je suppose que l'exemple le plus évident vous est déjà venu à l'esprit : ArrayList ! Voici comment nous en créons généralement un dans un programme :
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Comme vous pouvez le deviner, une caractéristique de cette liste est que nous ne pouvons pas tout y mettre : elle fonctionne exclusivement avec des objets String . Faisons maintenant une petite digression dans l'histoire de Java et essayons de répondre à la question "pourquoi?" Pour ce faire, nous allons écrire notre propre version simplifiée de la classe ArrayList. Notre liste ne sait comment ajouter et récupérer des données qu'à partir d'un tableau interne :
public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Supposons que nous voulions que notre liste ne stocke que des Integer s. Nous n'utilisons pas de type générique. Nous ne voulons pas inclure une vérification explicite "instanceof Integer " dans la méthode add() . Si nous le faisions, toute notre classe ne conviendrait qu'à Integer , et nous devrions écrire une classe similaire pour tous les autres types de données dans le monde ! Nous comptons sur nos programmeurs, et laissons juste un commentaire dans le code pour nous assurer qu'ils n'ajoutent rien de ce que nous ne voulons pas :
// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
L'un des programmeurs a raté ce commentaire et a mis par inadvertance plusieurs chaînes dans une liste de nombres, puis a calculé leur somme :
public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Sortie console :
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
      at Main.main (Main.java:14)
Quel est le pire aspect de cette situation ? Certainement pas l'insouciance du programmeur. Le pire, c'est qu'un code incorrect s'est retrouvé à une place importante dans notre programme et a été compilé avec succès. Maintenant, nous ne rencontrerons pas le bogue lors de l'écriture du code, mais uniquement lors des tests (et c'est le meilleur des cas !). La correction des bogues dans les étapes ultérieures du développement coûte beaucoup plus cher, à la fois en termes d'argent et de temps. C'est précisément là que les génériques nous profitent : une classe générique permet au programmeur malchanceux de détecter immédiatement l'erreur. Le programme ne compile tout simplement pas !
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();

       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Le programmeur réalise immédiatement son erreur et s'améliore instantanément. Soit dit en passant, nous n'avons pas eu à créer notre propre classe List pour voir ce genre d'erreur. Supprimez simplement les crochets angulaires et tapez ( <Integer> ) à partir d'une ArrayList ordinaire !
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Sortie console :
300
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
     at Main.main(Main.java:16)
En d'autres termes, même en utilisant les mécanismes "natifs" de Java, nous pouvons faire ce genre d'erreur et créer une collection non sécurisée. Cependant, si nous collons ce code dans un IDE, nous obtenons un avertissement : "Appel non vérifié pour ajouter (E) en tant que membre du type brut de java.util.List" On nous dit que quelque chose peut mal tourner lors de l'ajout d'un élément à une collection qui n'a pas de type générique. Mais que signifie l'expression "type brut" ? Un type brut est une classe générique dont le type a été supprimé. En d'autres termes, List myList1 est un type brut . L'opposé d'un type brut est un type générique - une classe générique avec une indication du ou des types paramétrés . Par exemple, List<String> myList1. Vous pourriez vous demander pourquoi le langage autorise l'utilisation de types bruts ? La raison est simple. Les créateurs de Java ont laissé la prise en charge des types bruts dans le langage afin d'éviter de créer des problèmes de compatibilité. Au moment de la sortie de Java 5.0 (les génériques sont apparus pour la première fois dans cette version), beaucoup de code avait déjà été écrit en utilisant des types bruts . En conséquence, ce mécanisme est toujours pris en charge aujourd'hui. Nous avons mentionné à plusieurs reprises le livre classique de Joshua Bloch "Effective Java" dans les leçons. En tant que l'un des créateurs du langage, il n'a pas sauté les types bruts et les types génériques dans son livre. Que sont les génériques en Java ?  - 2Le chapitre 23 du livre a un titre très éloquent : "N'utilisez pas de types bruts dans le nouveau code" C'est ce dont vous devez vous souvenir. Lorsque vous utilisez des classes génériques, ne transformez jamais un type générique en type brut .

Méthodes génériques

Java vous permet de paramétrer des méthodes individuelles en créant des méthodes dites génériques. En quoi ces méthodes sont-elles utiles ? Surtout, ils sont utiles dans la mesure où ils vous permettent de travailler avec différents types de paramètres de méthode. Si la même logique peut être appliquée en toute sécurité à différents types, une méthode générique peut être une excellente solution. Considérez ceci comme un exemple très simple : supposons que nous ayons une liste appelée myList1 . Nous voulons supprimer toutes les valeurs de la liste et remplir tous les espaces vides avec de nouvelles valeurs. Voici à quoi ressemble notre classe avec une méthode générique :
public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Attention à la syntaxe. Ça a l'air un peu inhabituel :
public static <T> void fill(List<T> list, T val)
On écrit <T> devant le type de retour. Cela indique que nous avons affaire à une méthode générique. Dans ce cas, la méthode accepte 2 paramètres en entrée : une liste d'objets T et un autre objet T séparé. En utilisant <T>, on paramètre les types de paramètres de la méthode : on ne peut pas passer une liste de Strings et un Integer. Une liste de chaînes et une chaîne, une liste d'entiers et un entier, une liste de nos propres objets Cat et un autre objet Cat - c'est ce que nous devons faire. La méthode main() illustre comment la méthode fill() peut être facilement utilisée pour travailler avec différents types de données. Tout d'abord, nous utilisons la méthode avec une liste de chaînes et une chaîne en entrée, puis avec une liste d'entiers et un entier. Sortie console :
[New String, New String, New String] [888, 888, 888]
Imaginez si nous n'avions pas de méthodes génériques et avions besoin de la logique de la méthode fill() pour 30 classes différentes. Il faudrait écrire la même méthode 30 fois pour différents types de données ! Mais grâce aux méthodes génériques, nous pouvons réutiliser notre code ! :)

Classes génériques

Vous n'êtes pas limité aux classes génériques fournies dans les bibliothèques Java standard — vous pouvez créer les vôtres ! Voici un exemple simple :
public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());

       stringBox.set(12345); // Compilation error!
   }
}
Notre classe Box<T> est une classe générique. Une fois que nous avons attribué un type de données ( <T> ) lors de la création, nous ne sommes plus en mesure d'y placer des objets d'autres types. Cela peut être vu dans l'exemple. Lors de la création de notre objet, nous avons indiqué qu'il fonctionnerait avec des Strings :
Box<String> stringBox = new Box<>();
Et dans la dernière ligne de code, quand on essaie de mettre le nombre 12345 à l'intérieur de la case, on obtient une erreur de compilation ! C'est si facile! Nous avons créé notre propre classe générique ! :) Avec cela, la leçon d'aujourd'hui touche à sa fin. Mais nous ne disons pas adieu aux génériques ! Dans les prochaines leçons, nous parlerons de fonctionnalités plus avancées, alors ne vous en allez pas ! ) Pour renforcer ce que vous avez appris, nous vous suggérons de regarder une leçon vidéo de notre cours Java
Bonne réussite dans vos études ! :)
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires