Publié par

Il y a 8 mois -

Temps de lecture 7 minutes

Retour aux bases : Java Generics

Les génériques sont une fonctionnalité qui a été ajoutée au langage de programmation Java en 2004 dans la version J2SE 5.0. Ils ont été conçus pour étendre le système de types de Java afin de permettre d’opérer sur des objets de différents types tout en offrant une sécurité à la compilation. Ils sont sortis il y a longtemps mais ça reste toujours une partie non maitrisée par de nombreux développeurs et revenir dessus est toujours utile.

Les méthodes et classes génériques Java permettent aux programmeurs de spécifier, avec une seule déclaration de méthode ou avec une seule déclaration de classe, un ensemble de types associés. Les génériques offrent également une type safety au moment de la compilation qui permet aux programmeurs de détecter les types non valides.

Avant de commencer, voyons le cas d’utilisation des génériques java.

List<Character> letters = Arrays.asList('a', 'b', 'c');
for (c Character: letters) {
  System.out.println("Letter: " + c); // imprimera toutes les lettres de la liste
}
letters.add(1); // erreur de compilation, la liste n'accepte pas le type int

Dans cet exemple, nous utilisons <> (l’opérateur diamant) pour spécifier le type de liste. Par conséquent, nous ne pouvons pas insérer un entier ou quoi que ce soit qui ne soit pas Character ou sa classe enfant.

Cas d’utilisation

Les génériques peuvent être utilisés pour une classe et une méthode :

Une déclaration de classe générique ressemble à une déclaration de classe non générique, sauf que le nom de classe est suivi d’une section de paramètre de type. La section des paramètres de type d’une classe générique peut avoir un ou plusieurs paramètres de type séparés par des virgules. Ces classes sont appelées classes paramétrées ou types paramétrés car elles acceptent un ou plusieurs paramètres.

public interface List<E> extends Collection<E> {
    boolean add(E e);
}

public static void main(String args[]) {
    List<Integer> integers = new ArrayList<>();
    integers.add(1); // OK
    integers.add("string"); // erreur de compilation, le type String n'est pas autorisé
}

Vous pouvez écrire une seule déclaration de méthode générique qui peut être appelée avec des arguments de différents types. En fonction des types d’arguments passés à la méthode générique, le compilateur gère chaque appel de méthode de manière appropriée.

public class GenericMethodTest {
   // méthode générique printList
   public static <E> void printList(List<E> list ) {
      // afficher les éléments de liste
      for(E element : list) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }

   public static void main(String args[]) {
      // Créer une liste d'entiers et de caractères
      List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
      List<Character> charList = Arrays.asList('J', 'A', 'V', 'A');

      System.out.println("List intList contains:");
      printList(intList);   // passer une liste entière

      System.out.println("\List charList contains:");
      printList(charList);   // passer une liste de Character
   }
}
Output
List intList contains:
1 2 3 4 5 6 7
List charList contains:
J A V A

Sous-typage et principe de substitution

Le sous-typage est une caractéristique clé des langages orientés objet tels que Java. En Java, un type est un sous-type d’un autre s’il est lié par une clause extends ou implements, le lien entre les types peut être indirect (ArrayListListCollectionObject). Voici quelques exemples :

  • Integer est un sous-type de Number
  • Double est un sous-type de Number
  • ArrayList est un sous-type de List
  • tout type est un sous-type de Object
  • ArrayList et List sont tous deux des sous-type de Collection

Le principe de substitution stipule qu’une variable d’un type donné peut se voir attribuer une valeur de n’importe quel sous-type de ce type, et une méthode avec un paramètre d’un type donné peut être invoquée avec un argument de n’importe quel sous-type de ce type.

Par exemple, si nous avons une collection de nombres, nous pouvons y ajouter un entier ou un double, car Integer et Double sont des sous-types de Number
List<Number> nums = new ArrayList<Number>(); 
nums.add(2); // OK
nums.add(3.14); // OK
nums.add("abcd"); // NOT OK, String n'est pas un sous-type de Number

Ici, le sous-typage est utilisé de deux manières pour chaque appel de méthode. Le premier appel est autorisé car nums a le type List, qui est un sous-type de Collection, et 2 a le type Integer (grâce au boxing), qui est un sous-type de Number. Le deuxième appel est également autorisé. Dans les deux appels, le E dans List est considéré comme un Number.

Wildcards

<? extends E>

L’interface Collection définit la méthode addAll, qui ajoute tous les membres d’une collection à une autre collection :

interface Collection<E> { … 
	public boolean addAll(Collection<? extends E> c); 
	… 
}
De toute évidence, étant donnée une collection d’éléments de type E, il est autorisé d’ajouter tous les membres d’une autre collection avec des éléments de type E. L’expression <? extends E> signifie qu’il est également autorisé d’ajouter tous les membres d’une collection avec des éléments de tout type qui est un sous-type de E. Le point d’interrogation est appelé un wildcard, car il représente un type qui est un sous-type de E. Voici un exemple.
Nous créons une liste de nombres vide, et y ajoutons d’abord une liste d’entiers puis une liste de doubles:
List<Number> nums = new ArrayList<>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
nums.addAll(ints); // OK
nums.addAll(dbls); // OK

Le premier appel est autorisé car nums a le type List, qui est un sous-type de Collection, et ints a le type List, qui est un sous-type de Collection<? extends Number>. Le deuxième appel est également autorisé pour les mêmes raisons. Dans les deux appels, E est considéré comme un Number. Si la signature de la méthode pour addAll avait été écrite sans le wildcard, les appels pour ajouter des listes d’entiers et des doubles à une liste de nombres n’auraient pas été autorisés; vous auriez seulement pu ajouter une liste qui a été explicitement déclarée comme une liste de nombres.

<? super T>

L’instruction <? super T> signifie que la liste de destination peut avoir des éléments de tout type qui est un super-type de T, tout comme la liste source peut avoir des éléments de tout type qui est un sous-type de T, vous trouverez l’exemple d’utilisation dans la partie suivante.

Le principe Get and Put

Il peut être judicieux d’insérer des wildcards dans la mesure du possible, mais comment décidez-vous quel wildcard utiliser ? Où devez-vous utiliser le wildcard extends, où devez-vous utiliser super et où est-il inapproprié d’utiliser un wildcard ? Heureusement, un principe simple détermine celui qui convient.

Le principe Get and Put stipule qu’il faut utilisez un wildcard extends lorsque vous extrayez uniquement des valeurs d’une structure, utilisez le wildcard super lorsque vous placez uniquement des valeurs dans une structure, et n’utilisez pas de wildcard lorsque vous avez besoin de faire les deux à la fois.

// L’exemple du principe Get
public static void main(String[] args) {
    List<Integer> integers = asList(1, 2, 3);
    printList(integers); // OK
    List<Double> doubles = asList(1d, 2d, 3d);
    printList(doubles); // OK
    List<Number> numbers = asList(1, 2d, 0xFF);
    printList(numbers); // OK
}

public static void printList(List<? extends Number> list) {
    for (Number n : list) {
        System.out.println(n);
    }
}

Il ne serait pas possible d’imprimer des entiers et des doubles sans <? extends Number>.

// L’exemple du principe Put
public static void main(String[] args) {
    List<Integer> integers = new ArrayList<>();
    addToList(integers, 1); // OK
    List<Number> numbers = new ArrayList<>();
    addToList(numbers, 4); // OK
    List<Object> objects = new ArrayList<>();
    addToList(objects, 7); // OK
}

public static void addToList(List<? super Integer> list, int i) {
    list.add(i);
}

Il ne serait pas possible d’ajouter un entier aux nombres et aux objets sans <? super Integer>.

Conclusion

Dans cet article, nous avons appris comment utiliser les génériques java, les wildcards, le principe de substitution et le principe Get and Put. Les génériques sont une fonctionnalité utile du langage java. Et des principes tels que le principe Get and Put ou le principe de substitution peuvent aider à les utiliser correctement.

Publié par

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Sapient, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.